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
|
@@ -36375,7 +36375,7 @@ var require_libvips = __commonJS((exports, module) => {
|
|
|
36375
36375
|
SPDX-License-Identifier: Apache-2.0
|
|
36376
36376
|
*/
|
|
36377
36377
|
var { spawnSync: spawnSync6 } = __require("node:child_process");
|
|
36378
|
-
var { createHash:
|
|
36378
|
+
var { createHash: createHash11 } = __require("node:crypto");
|
|
36379
36379
|
var semverCoerce = require_coerce2();
|
|
36380
36380
|
var semverGreaterThanOrEqualTo = require_gte2();
|
|
36381
36381
|
var semverSatisfies = require_satisfies2();
|
|
@@ -36463,7 +36463,7 @@ var require_libvips = __commonJS((exports, module) => {
|
|
|
36463
36463
|
}
|
|
36464
36464
|
return false;
|
|
36465
36465
|
};
|
|
36466
|
-
var sha512 = (s) =>
|
|
36466
|
+
var sha512 = (s) => createHash11("sha512").update(s).digest("hex");
|
|
36467
36467
|
var yarnLocator = () => {
|
|
36468
36468
|
try {
|
|
36469
36469
|
const identHash = sha512(`imgsharp-libvips-${buildPlatformArch()}`);
|
|
@@ -75917,11 +75917,14 @@ var EmbeddingConnectionConfigSchema = exports_external.object({
|
|
|
75917
75917
|
apiKey: symbolicOrWarnApiKey("embedding.apiKey").optional(),
|
|
75918
75918
|
dimension: positiveInt.max(4096).optional(),
|
|
75919
75919
|
localModel: exports_external.string().min(1).optional(),
|
|
75920
|
+
maxInputTokens: positiveInt.optional(),
|
|
75920
75921
|
maxTokens: positiveInt.optional(),
|
|
75921
75922
|
batchSize: positiveInt.optional(),
|
|
75922
75923
|
chunkSize: positiveInt.optional(),
|
|
75923
75924
|
contextLength: positiveInt.optional(),
|
|
75924
|
-
ollamaOptions: EmbeddingOllamaOptionsSchema.optional()
|
|
75925
|
+
ollamaOptions: EmbeddingOllamaOptionsSchema.optional(),
|
|
75926
|
+
timeoutMs: positiveInt.optional(),
|
|
75927
|
+
concurrency: positiveInt.max(16).optional()
|
|
75925
75928
|
}).passthrough();
|
|
75926
75929
|
|
|
75927
75930
|
// src/integrations/harnesses/ids.ts
|
|
@@ -76994,8 +76997,21 @@ function releaseLockRaw(lockPath) {
|
|
|
76994
76997
|
function tryAcquireLockSync(lockPath, payload) {
|
|
76995
76998
|
return withLockOperationMutex(lockPath, () => tryAcquireLockRaw(lockPath, payload));
|
|
76996
76999
|
}
|
|
77000
|
+
function launcherPidFromEnv() {
|
|
77001
|
+
const raw = process.env.AKM_LAUNCHER_PID;
|
|
77002
|
+
if (!raw)
|
|
77003
|
+
return;
|
|
77004
|
+
const pid = Number.parseInt(raw, 10);
|
|
77005
|
+
return Number.isInteger(pid) && pid > 0 ? pid : undefined;
|
|
77006
|
+
}
|
|
76997
77007
|
function createLockPayload(metadata = {}) {
|
|
76998
|
-
|
|
77008
|
+
const launcherPid = launcherPidFromEnv();
|
|
77009
|
+
return JSON.stringify({
|
|
77010
|
+
...metadata,
|
|
77011
|
+
pid: process.pid,
|
|
77012
|
+
...launcherPid !== undefined ? { launcherPid } : {},
|
|
77013
|
+
lockId: randomUUID()
|
|
77014
|
+
});
|
|
76999
77015
|
}
|
|
77000
77016
|
function probeLock(lockPath, opts) {
|
|
77001
77017
|
let snapshot;
|
|
@@ -77011,17 +77027,17 @@ function probeLock(lockPath, opts) {
|
|
|
77011
77027
|
return { state: "absent" };
|
|
77012
77028
|
const { rawContent, identity: identity3 } = snapshot;
|
|
77013
77029
|
const ageMs = Date.now() - identity3.mtimeMs;
|
|
77014
|
-
const holderPid =
|
|
77030
|
+
const { holderPid, launcherPid } = extractLockIdentity(rawContent);
|
|
77015
77031
|
if (holderPid === undefined) {
|
|
77016
77032
|
return { state: "stale", reason: "invalid_pid", ageMs, rawContent, identity: identity3 };
|
|
77017
77033
|
}
|
|
77018
77034
|
if (!isProcessAlive(holderPid)) {
|
|
77019
|
-
return { state: "stale", reason: "pid_dead", holderPid, ageMs, rawContent, identity: identity3 };
|
|
77035
|
+
return { state: "stale", reason: "pid_dead", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
|
|
77020
77036
|
}
|
|
77021
77037
|
if (opts?.staleAfterMs !== undefined && ageMs > opts.staleAfterMs) {
|
|
77022
|
-
return { state: "stale", reason: "age_exceeded", holderPid, ageMs, rawContent, identity: identity3 };
|
|
77038
|
+
return { state: "stale", reason: "age_exceeded", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
|
|
77023
77039
|
}
|
|
77024
|
-
return { state: "held", holderPid, ageMs, rawContent, identity: identity3 };
|
|
77040
|
+
return { state: "held", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
|
|
77025
77041
|
}
|
|
77026
77042
|
function reclaimStaleLock(lockPath, probe, options) {
|
|
77027
77043
|
if (probe.rawContent === undefined || probe.identity === undefined)
|
|
@@ -77089,21 +77105,25 @@ function releaseLock(ownership) {
|
|
|
77089
77105
|
}
|
|
77090
77106
|
});
|
|
77091
77107
|
}
|
|
77092
|
-
function
|
|
77108
|
+
function extractLockIdentity(content) {
|
|
77093
77109
|
const trimmed = content.trim();
|
|
77094
77110
|
if (!trimmed)
|
|
77095
|
-
return;
|
|
77111
|
+
return {};
|
|
77096
77112
|
if (trimmed.startsWith("{")) {
|
|
77097
77113
|
try {
|
|
77098
77114
|
const parsed = JSON.parse(trimmed);
|
|
77099
77115
|
const pid2 = typeof parsed.pid === "number" ? parsed.pid : Number.NaN;
|
|
77100
|
-
|
|
77116
|
+
const rawLauncherPid = typeof parsed.launcherPid === "number" ? parsed.launcherPid : Number.NaN;
|
|
77117
|
+
return {
|
|
77118
|
+
holderPid: Number.isInteger(pid2) && pid2 > 0 ? pid2 : undefined,
|
|
77119
|
+
launcherPid: Number.isInteger(rawLauncherPid) && rawLauncherPid > 0 ? rawLauncherPid : undefined
|
|
77120
|
+
};
|
|
77101
77121
|
} catch {
|
|
77102
|
-
return;
|
|
77122
|
+
return {};
|
|
77103
77123
|
}
|
|
77104
77124
|
}
|
|
77105
77125
|
const pid = Number.parseInt(trimmed, 10);
|
|
77106
|
-
return Number.isInteger(pid) && pid > 0 ? pid : undefined;
|
|
77126
|
+
return { holderPid: Number.isInteger(pid) && pid > 0 ? pid : undefined };
|
|
77107
77127
|
}
|
|
77108
77128
|
|
|
77109
77129
|
// src/core/config/config-io.ts
|
|
@@ -95737,7 +95757,7 @@ function listTxnJournalsTolerant(predicate) {
|
|
|
95737
95757
|
}
|
|
95738
95758
|
|
|
95739
95759
|
// src/commands/proposal/repository.ts
|
|
95740
|
-
import { createHash as
|
|
95760
|
+
import { createHash as createHash11, randomUUID as randomUUID6 } from "node:crypto";
|
|
95741
95761
|
import fs55 from "node:fs";
|
|
95742
95762
|
init_dist();
|
|
95743
95763
|
import path66 from "node:path";
|
|
@@ -95803,6 +95823,16 @@ import fs53 from "node:fs";
|
|
|
95803
95823
|
import path64 from "node:path";
|
|
95804
95824
|
init_errors();
|
|
95805
95825
|
init_paths();
|
|
95826
|
+
|
|
95827
|
+
// src/core/run-lock.ts
|
|
95828
|
+
init_errors();
|
|
95829
|
+
function formatLockHolderPid(holder) {
|
|
95830
|
+
if (holder.pid === null)
|
|
95831
|
+
return "unknown";
|
|
95832
|
+
return holder.launcherPid !== null ? `${holder.pid} (launcher ${holder.launcherPid})` : String(holder.pid);
|
|
95833
|
+
}
|
|
95834
|
+
|
|
95835
|
+
// src/indexer/index-written-assets.ts
|
|
95806
95836
|
init_warn();
|
|
95807
95837
|
|
|
95808
95838
|
// src/storage/repositories/index-connection.ts
|
|
@@ -96057,6 +96087,15 @@ function isCanonicalIndexGeneration(db) {
|
|
|
96057
96087
|
init_errors();
|
|
96058
96088
|
init_warn();
|
|
96059
96089
|
|
|
96090
|
+
// src/storage/repositories/embedding-salvage-repository.ts
|
|
96091
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
96092
|
+
|
|
96093
|
+
// src/storage/repositories/embeddings-repository.ts
|
|
96094
|
+
function blobToEmbedding(blob) {
|
|
96095
|
+
const f32 = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
|
|
96096
|
+
return Array.from(f32);
|
|
96097
|
+
}
|
|
96098
|
+
|
|
96060
96099
|
// src/storage/repositories/index-meta-repository.ts
|
|
96061
96100
|
function getMeta(db, key) {
|
|
96062
96101
|
const row = db.prepare("SELECT value FROM index_meta WHERE key = ?").get(key);
|
|
@@ -96069,6 +96108,64 @@ function deleteMeta(db, key) {
|
|
|
96069
96108
|
db.prepare("DELETE FROM index_meta WHERE key = ?").run(key);
|
|
96070
96109
|
}
|
|
96071
96110
|
|
|
96111
|
+
// src/storage/repositories/index-sql.ts
|
|
96112
|
+
var SQLITE_CHUNK_SIZE = 500;
|
|
96113
|
+
|
|
96114
|
+
// src/storage/repositories/embedding-salvage-repository.ts
|
|
96115
|
+
function hashEmbeddableText(searchText) {
|
|
96116
|
+
return createHash9("sha256").update(searchText, "utf8").digest("hex");
|
|
96117
|
+
}
|
|
96118
|
+
function purgeEmbeddingSalvage(db) {
|
|
96119
|
+
db.exec("DELETE FROM embedding_salvage");
|
|
96120
|
+
}
|
|
96121
|
+
function relabelEmbeddingSalvageFingerprint(db, fromFingerprint, toFingerprint) {
|
|
96122
|
+
db.prepare("UPDATE embedding_salvage SET fingerprint = ? WHERE fingerprint = ?").run(toFingerprint, fromFingerprint);
|
|
96123
|
+
}
|
|
96124
|
+
function reuseSalvagedEmbeddings(db, entries, fingerprint, writeReused) {
|
|
96125
|
+
if (entries.length === 0)
|
|
96126
|
+
return { reusedCount: 0, remaining: [] };
|
|
96127
|
+
const anySalvageForFingerprint = db.prepare("SELECT 1 FROM embedding_salvage WHERE fingerprint = ? LIMIT 1").get(fingerprint);
|
|
96128
|
+
if (!anySalvageForFingerprint)
|
|
96129
|
+
return { reusedCount: 0, remaining: [...entries] };
|
|
96130
|
+
const hashes = entries.map((entry) => hashEmbeddableText(entry.searchText));
|
|
96131
|
+
const salvageByHash = new Map;
|
|
96132
|
+
const uniqueHashes = [...new Set(hashes)];
|
|
96133
|
+
for (let offset = 0;offset < uniqueHashes.length; offset += SQLITE_CHUNK_SIZE) {
|
|
96134
|
+
const chunk = uniqueHashes.slice(offset, offset + SQLITE_CHUNK_SIZE);
|
|
96135
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
96136
|
+
const rows = db.prepare(`SELECT content_hash AS contentHash, embedding FROM embedding_salvage WHERE fingerprint = ? AND content_hash IN (${placeholders})`).all(fingerprint, ...chunk);
|
|
96137
|
+
for (const row of rows)
|
|
96138
|
+
salvageByHash.set(row.contentHash, row.embedding);
|
|
96139
|
+
}
|
|
96140
|
+
if (salvageByHash.size === 0)
|
|
96141
|
+
return { reusedCount: 0, remaining: [...entries] };
|
|
96142
|
+
let reusedCount = 0;
|
|
96143
|
+
const remaining = [];
|
|
96144
|
+
for (let offset = 0;offset < entries.length; offset += SQLITE_CHUNK_SIZE) {
|
|
96145
|
+
const end = Math.min(offset + SQLITE_CHUNK_SIZE, entries.length);
|
|
96146
|
+
const chunkMatches = [];
|
|
96147
|
+
for (let i = offset;i < end; i++) {
|
|
96148
|
+
const entry = entries[i];
|
|
96149
|
+
const blob = salvageByHash.get(hashes[i]);
|
|
96150
|
+
if (blob)
|
|
96151
|
+
chunkMatches.push({ entry, blob });
|
|
96152
|
+
else
|
|
96153
|
+
remaining.push(entry);
|
|
96154
|
+
}
|
|
96155
|
+
if (chunkMatches.length === 0)
|
|
96156
|
+
continue;
|
|
96157
|
+
db.transaction(() => {
|
|
96158
|
+
for (const { entry, blob } of chunkMatches) {
|
|
96159
|
+
if (writeReused(entry, blobToEmbedding(blob)))
|
|
96160
|
+
reusedCount++;
|
|
96161
|
+
else
|
|
96162
|
+
remaining.push(entry);
|
|
96163
|
+
}
|
|
96164
|
+
})();
|
|
96165
|
+
}
|
|
96166
|
+
return { reusedCount, remaining };
|
|
96167
|
+
}
|
|
96168
|
+
|
|
96072
96169
|
// src/storage/repositories/index-vec-repository.ts
|
|
96073
96170
|
import { createRequire as createRequire4 } from "node:module";
|
|
96074
96171
|
|
|
@@ -96110,9 +96207,6 @@ function cosineSimilarity(a, b) {
|
|
|
96110
96207
|
return denom === 0 ? 0 : dot / denom;
|
|
96111
96208
|
}
|
|
96112
96209
|
|
|
96113
|
-
// src/storage/repositories/index-sql.ts
|
|
96114
|
-
var SQLITE_CHUNK_SIZE = 500;
|
|
96115
|
-
|
|
96116
96210
|
// src/storage/repositories/index-vec-repository.ts
|
|
96117
96211
|
var vecStatus = new WeakMap;
|
|
96118
96212
|
function loadVecExtension(db) {
|
|
@@ -96372,11 +96466,11 @@ init_warn();
|
|
|
96372
96466
|
|
|
96373
96467
|
// src/core/asset/markdown-fragments.ts
|
|
96374
96468
|
init_markdown();
|
|
96375
|
-
import { createHash as
|
|
96469
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
96376
96470
|
var MARKDOWN_FRAGMENT_MAX_CHARS = 1600;
|
|
96377
96471
|
var MARKDOWN_FRAGMENT_PREFIX = "akm-fragment-";
|
|
96378
96472
|
function hash3(text) {
|
|
96379
|
-
return
|
|
96473
|
+
return createHash10("sha256").update(text).digest("hex");
|
|
96380
96474
|
}
|
|
96381
96475
|
function uniqueSlugs(body) {
|
|
96382
96476
|
const out = new Map;
|
|
@@ -96657,6 +96751,7 @@ function getEmbeddableEntryCount(db) {
|
|
|
96657
96751
|
}
|
|
96658
96752
|
|
|
96659
96753
|
// src/indexer/materialize-embeddings.ts
|
|
96754
|
+
init_paths();
|
|
96660
96755
|
init_warn();
|
|
96661
96756
|
|
|
96662
96757
|
// src/llm/embedders/cache.ts
|
|
@@ -96896,6 +96991,39 @@ var DEFAULT_TOKEN_BUDGET = 8000;
|
|
|
96896
96991
|
function estimateTokenCount(text) {
|
|
96897
96992
|
return Math.round(text.length / 4);
|
|
96898
96993
|
}
|
|
96994
|
+
var DEFAULT_MAX_INPUT_TOKENS = 512;
|
|
96995
|
+
function capEmbeddingText(text, maxTokens) {
|
|
96996
|
+
if (estimateTokenCount(text) <= maxTokens)
|
|
96997
|
+
return { text, truncated: false };
|
|
96998
|
+
const charBudget = Math.max(0, maxTokens * 4);
|
|
96999
|
+
let cut = Math.min(charBudget, text.length);
|
|
97000
|
+
if (cut > 0 && cut < text.length) {
|
|
97001
|
+
const code = text.charCodeAt(cut);
|
|
97002
|
+
if (code >= 56320 && code <= 57343)
|
|
97003
|
+
cut -= 1;
|
|
97004
|
+
}
|
|
97005
|
+
return { text: text.slice(0, cut), truncated: true };
|
|
97006
|
+
}
|
|
97007
|
+
var DEFAULT_EMBEDDING_TIMEOUT_MS = 120000;
|
|
97008
|
+
function resolveEmbeddingTimeoutMs(config) {
|
|
97009
|
+
return config.timeoutMs ?? DEFAULT_EMBEDDING_TIMEOUT_MS;
|
|
97010
|
+
}
|
|
97011
|
+
function scaleEmbeddingTimeoutMs(timeoutMs, requestTokens, tokenBudget) {
|
|
97012
|
+
const scaled = tokenBudget > 0 ? timeoutMs * (requestTokens / tokenBudget) : timeoutMs;
|
|
97013
|
+
return Math.min(Math.max(scaled, 30000), timeoutMs);
|
|
97014
|
+
}
|
|
97015
|
+
function isEmbeddingTimeoutError(err) {
|
|
97016
|
+
if (!(err instanceof Error))
|
|
97017
|
+
return false;
|
|
97018
|
+
if (err.name === "BodyReadTimeoutError")
|
|
97019
|
+
return true;
|
|
97020
|
+
return err.message.startsWith("Request timed out after ");
|
|
97021
|
+
}
|
|
97022
|
+
var embeddingTimeoutBackoffOverrideForTests;
|
|
97023
|
+
function embeddingTimeoutRetryBackoffMs(timeoutAttempt = 0) {
|
|
97024
|
+
const { baseMs, maxMs } = embeddingTimeoutBackoffOverrideForTests ?? { baseMs: 5000, maxMs: 60000 };
|
|
97025
|
+
return backoffDelay(timeoutAttempt, baseMs, maxMs);
|
|
97026
|
+
}
|
|
96899
97027
|
|
|
96900
97028
|
class ContextExceededError extends Error {
|
|
96901
97029
|
constructor(message) {
|
|
@@ -96903,13 +97031,15 @@ class ContextExceededError extends Error {
|
|
|
96903
97031
|
this.name = "ContextExceededError";
|
|
96904
97032
|
}
|
|
96905
97033
|
}
|
|
96906
|
-
var CONTEXT_EXCEEDED_PATTERN = /exceed_context_size_error|context size|context length|too many tokens/i;
|
|
97034
|
+
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;
|
|
96907
97035
|
function isContextExceededResponse(status, body) {
|
|
96908
97036
|
if (status === 413)
|
|
96909
97037
|
return true;
|
|
96910
97038
|
return CONTEXT_EXCEEDED_PATTERN.test(body);
|
|
96911
97039
|
}
|
|
96912
97040
|
function resolveEmbeddingConcurrency(config) {
|
|
97041
|
+
if (typeof config.concurrency === "number")
|
|
97042
|
+
return config.concurrency;
|
|
96913
97043
|
return defaultConcurrencyForEndpoint(config.endpoint);
|
|
96914
97044
|
}
|
|
96915
97045
|
function buildTokenBoundedBatches(texts, tokenBudget, maxCount) {
|
|
@@ -96965,20 +97095,21 @@ class RemoteEmbedder {
|
|
|
96965
97095
|
if (ollamaOpts) {
|
|
96966
97096
|
body.options = ollamaOpts;
|
|
96967
97097
|
}
|
|
97098
|
+
const timeoutMs = resolveEmbeddingTimeoutMs(this.config);
|
|
96968
97099
|
const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(this.endpoint), {
|
|
96969
97100
|
method: "POST",
|
|
96970
97101
|
headers,
|
|
96971
97102
|
body: JSON.stringify(body)
|
|
96972
|
-
},
|
|
97103
|
+
}, timeoutMs, signal);
|
|
96973
97104
|
if (!response.ok) {
|
|
96974
|
-
const errBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs:
|
|
97105
|
+
const errBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }).catch((err) => {
|
|
96975
97106
|
if (signal?.aborted)
|
|
96976
97107
|
throw err;
|
|
96977
97108
|
return "";
|
|
96978
97109
|
});
|
|
96979
97110
|
throw new Error(`Embedding request failed (${response.status}): ${this.safeErrorBody(errBody)}`);
|
|
96980
97111
|
}
|
|
96981
|
-
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs:
|
|
97112
|
+
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }));
|
|
96982
97113
|
if (!json.data?.[0]?.embedding) {
|
|
96983
97114
|
throw new Error(`Unexpected embedding response format: missing data[0].embedding.${embeddingEndpointPathHint(this.endpoint)}`);
|
|
96984
97115
|
}
|
|
@@ -96990,9 +97121,10 @@ class RemoteEmbedder {
|
|
|
96990
97121
|
const results = new Array(texts.length).fill(undefined);
|
|
96991
97122
|
const headers = this.buildHeaders();
|
|
96992
97123
|
const ollamaOpts = resolveOllamaOptions(this.config);
|
|
96993
|
-
const tokenBudget = this.config.maxTokens ??
|
|
97124
|
+
const tokenBudget = this.config.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
96994
97125
|
const maxCount = this.config.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
96995
97126
|
const textBatches = buildTokenBoundedBatches(texts, tokenBudget, maxCount);
|
|
97127
|
+
const configuredTimeoutMs = resolveEmbeddingTimeoutMs(this.config);
|
|
96996
97128
|
const dispatchAbort = new AbortController;
|
|
96997
97129
|
const stopDispatch = (reason) => {
|
|
96998
97130
|
if (!dispatchAbort.signal.aborted)
|
|
@@ -97008,66 +97140,132 @@ class RemoteEmbedder {
|
|
|
97008
97140
|
}
|
|
97009
97141
|
}
|
|
97010
97142
|
let firstOnBatchError;
|
|
97011
|
-
const commitBatch = (indices, embeddings, model) => {
|
|
97143
|
+
const commitBatch = (indices, embeddings, model, outcome) => {
|
|
97012
97144
|
if (!onBatch)
|
|
97013
97145
|
return;
|
|
97014
97146
|
if (firstOnBatchError !== undefined)
|
|
97015
97147
|
return;
|
|
97016
97148
|
try {
|
|
97017
|
-
onBatch(indices, embeddings, model);
|
|
97149
|
+
onBatch(indices, embeddings, model, outcome);
|
|
97018
97150
|
} catch (err) {
|
|
97019
97151
|
firstOnBatchError = err;
|
|
97020
97152
|
stopDispatch(err);
|
|
97021
97153
|
}
|
|
97022
97154
|
};
|
|
97023
|
-
const requestAndCommit = async (indices) => {
|
|
97155
|
+
const requestAndCommit = async (indices, batchIndex, isTimeoutRetry = false, timeoutAttempt = 0) => {
|
|
97156
|
+
if (dispatchAbort.signal.aborted)
|
|
97157
|
+
return;
|
|
97024
97158
|
const batch = indices.map((i) => texts[i]);
|
|
97159
|
+
const requestTokens = batch.reduce((sum, text) => sum + estimateTokenCount(text), 0);
|
|
97160
|
+
const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens, tokenBudget);
|
|
97161
|
+
const requestStart = Date.now();
|
|
97025
97162
|
let batchEmbeddings;
|
|
97026
97163
|
let responseModel;
|
|
97164
|
+
let outcome;
|
|
97165
|
+
let failureReason;
|
|
97027
97166
|
try {
|
|
97028
|
-
const { vectors, model } = await this.requestBatch(batch, headers, ollamaOpts, signal);
|
|
97167
|
+
const { vectors, model } = await this.requestBatch(batch, headers, ollamaOpts, requestTimeoutMs, signal);
|
|
97029
97168
|
for (let k2 = 0;k2 < indices.length; k2++) {
|
|
97030
97169
|
results[indices[k2]] = vectors[k2];
|
|
97031
97170
|
}
|
|
97032
97171
|
batchEmbeddings = indices.map((i) => results[i]);
|
|
97033
97172
|
responseModel = model;
|
|
97173
|
+
outcome = "stored";
|
|
97034
97174
|
} catch (err) {
|
|
97035
97175
|
if (signal?.aborted)
|
|
97036
97176
|
throw err;
|
|
97037
97177
|
if (err instanceof ContextExceededError && indices.length > 1) {
|
|
97038
97178
|
const mid = Math.ceil(indices.length / 2);
|
|
97039
|
-
await requestAndCommit(indices.slice(0, mid));
|
|
97040
|
-
await requestAndCommit(indices.slice(mid));
|
|
97179
|
+
await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt);
|
|
97180
|
+
await requestAndCommit(indices.slice(mid), batchIndex, false, timeoutAttempt);
|
|
97181
|
+
return;
|
|
97182
|
+
}
|
|
97183
|
+
const timedOut = isEmbeddingTimeoutError(err);
|
|
97184
|
+
if (timedOut && !isTimeoutRetry) {
|
|
97185
|
+
const backoffMs = embeddingTimeoutRetryBackoffMs(timeoutAttempt);
|
|
97186
|
+
warnVerbose(`[embed] batch of ${batch.length} document(s) timed out after ${requestTimeoutMs}ms; retrying once after a ${Math.round(backoffMs)}ms backoff`);
|
|
97187
|
+
commitBatch(indices, indices.map(() => {
|
|
97188
|
+
return;
|
|
97189
|
+
}), undefined, {
|
|
97190
|
+
batchIndex,
|
|
97191
|
+
batchCount: textBatches.length,
|
|
97192
|
+
docCount: indices.length,
|
|
97193
|
+
requestTokens,
|
|
97194
|
+
elapsedMs: backoffMs,
|
|
97195
|
+
outcome: "retrying",
|
|
97196
|
+
reason: "timed out"
|
|
97197
|
+
});
|
|
97198
|
+
await abortableDelay(backoffMs, signal, "embedding interrupted during retry backoff");
|
|
97199
|
+
if (!dispatchAbort.signal.aborted) {
|
|
97200
|
+
return requestAndCommit(indices, batchIndex, true, timeoutAttempt);
|
|
97201
|
+
}
|
|
97202
|
+
} else if (timedOut && indices.length > 1) {
|
|
97203
|
+
const mid = Math.ceil(indices.length / 2);
|
|
97204
|
+
await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt + 1);
|
|
97205
|
+
await requestAndCommit(indices.slice(mid), batchIndex, false, timeoutAttempt + 1);
|
|
97041
97206
|
return;
|
|
97042
97207
|
}
|
|
97043
97208
|
const message = err instanceof Error ? err.message : String(err);
|
|
97044
97209
|
const reason = err instanceof ContextExceededError ? "context-window-exceeded" : "batch-request-failed";
|
|
97045
|
-
|
|
97046
|
-
|
|
97047
|
-
|
|
97048
|
-
|
|
97210
|
+
const failureKind = reason === "batch-request-failed" ? timedOut ? "timeout" : "network-error" : undefined;
|
|
97211
|
+
let stopRequested = false;
|
|
97212
|
+
for (const [k2, idx] of indices.entries()) {
|
|
97213
|
+
if (onSkip?.({
|
|
97214
|
+
index: idx,
|
|
97215
|
+
reason,
|
|
97216
|
+
message,
|
|
97217
|
+
batchStart: k2 === 0,
|
|
97218
|
+
batchSize: indices.length,
|
|
97219
|
+
failureKind
|
|
97220
|
+
}) === false)
|
|
97221
|
+
stopRequested = true;
|
|
97222
|
+
}
|
|
97223
|
+
if (stopRequested)
|
|
97224
|
+
stopDispatch();
|
|
97049
97225
|
batchEmbeddings = indices.map(() => {
|
|
97050
97226
|
return;
|
|
97051
97227
|
});
|
|
97052
|
-
|
|
97053
|
-
|
|
97228
|
+
outcome = "failed";
|
|
97229
|
+
failureReason = message;
|
|
97230
|
+
}
|
|
97231
|
+
commitBatch(indices, batchEmbeddings, responseModel, {
|
|
97232
|
+
batchIndex,
|
|
97233
|
+
batchCount: textBatches.length,
|
|
97234
|
+
docCount: indices.length,
|
|
97235
|
+
requestTokens,
|
|
97236
|
+
elapsedMs: Date.now() - requestStart,
|
|
97237
|
+
outcome,
|
|
97238
|
+
reason: failureReason
|
|
97239
|
+
});
|
|
97054
97240
|
};
|
|
97055
|
-
const runProviderBatch = async (textBatch) => {
|
|
97241
|
+
const runProviderBatch = async (textBatch, batchIndex) => {
|
|
97056
97242
|
if (textBatch.oversized) {
|
|
97057
97243
|
const idx = textBatch.indices[0];
|
|
97058
97244
|
const estTokens = estimateTokenCount(texts[idx]);
|
|
97059
97245
|
onSkip?.({
|
|
97060
97246
|
index: idx,
|
|
97061
97247
|
reason: "context-window-exceeded",
|
|
97062
|
-
message: `Document estimated at ${estTokens} tokens exceeds the ${tokenBudget}-token embedding budget; skipped
|
|
97248
|
+
message: `Document estimated at ${estTokens} tokens exceeds the ${tokenBudget}-token embedding budget; skipped.`,
|
|
97249
|
+
batchStart: true,
|
|
97250
|
+
batchSize: 1
|
|
97251
|
+
});
|
|
97252
|
+
commitBatch([idx], [undefined], undefined, {
|
|
97253
|
+
batchIndex,
|
|
97254
|
+
batchCount: textBatches.length,
|
|
97255
|
+
docCount: 1,
|
|
97256
|
+
requestTokens: estTokens,
|
|
97257
|
+
elapsedMs: 0,
|
|
97258
|
+
outcome: "failed",
|
|
97259
|
+
reason: "oversized"
|
|
97063
97260
|
});
|
|
97064
|
-
commitBatch([idx], [undefined]);
|
|
97065
97261
|
return;
|
|
97066
97262
|
}
|
|
97067
|
-
await requestAndCommit(textBatch.indices);
|
|
97263
|
+
await requestAndCommit(textBatch.indices, batchIndex);
|
|
97068
97264
|
};
|
|
97069
97265
|
const concurrency = resolveEmbeddingConcurrency(this.config);
|
|
97070
|
-
await concurrentMap(textBatches, runProviderBatch, concurrency, {
|
|
97266
|
+
await concurrentMap(textBatches, (textBatch, i) => runProviderBatch(textBatch, i + 1), concurrency, {
|
|
97267
|
+
signal: dispatchAbort.signal
|
|
97268
|
+
});
|
|
97071
97269
|
if (callerAbortListener)
|
|
97072
97270
|
signal?.removeEventListener("abort", callerAbortListener);
|
|
97073
97271
|
if (signal?.aborted) {
|
|
@@ -97078,7 +97276,7 @@ class RemoteEmbedder {
|
|
|
97078
97276
|
}
|
|
97079
97277
|
return results;
|
|
97080
97278
|
}
|
|
97081
|
-
async requestBatch(batch, headers, ollamaOpts, signal) {
|
|
97279
|
+
async requestBatch(batch, headers, ollamaOpts, timeoutMs, signal) {
|
|
97082
97280
|
const body = {
|
|
97083
97281
|
input: batch,
|
|
97084
97282
|
model: this.model
|
|
@@ -97093,9 +97291,9 @@ class RemoteEmbedder {
|
|
|
97093
97291
|
method: "POST",
|
|
97094
97292
|
headers,
|
|
97095
97293
|
body: JSON.stringify(body)
|
|
97096
|
-
},
|
|
97294
|
+
}, timeoutMs, signal);
|
|
97097
97295
|
if (!response.ok) {
|
|
97098
|
-
const respBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs:
|
|
97296
|
+
const respBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }).catch((err) => {
|
|
97099
97297
|
if (signal?.aborted)
|
|
97100
97298
|
throw err;
|
|
97101
97299
|
return "";
|
|
@@ -97106,7 +97304,7 @@ class RemoteEmbedder {
|
|
|
97106
97304
|
}
|
|
97107
97305
|
throw new Error(message);
|
|
97108
97306
|
}
|
|
97109
|
-
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs:
|
|
97307
|
+
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }));
|
|
97110
97308
|
if (!json.data || json.data.length !== batch.length) {
|
|
97111
97309
|
throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}.${embeddingEndpointPathHint(this.endpoint)}`);
|
|
97112
97310
|
}
|
|
@@ -97175,6 +97373,15 @@ function resolveOllamaOptions(config) {
|
|
|
97175
97373
|
function hasRemoteEndpoint(config) {
|
|
97176
97374
|
return isHttpUrl(config.endpoint);
|
|
97177
97375
|
}
|
|
97376
|
+
function describeEmbeddingCredential(apiKey) {
|
|
97377
|
+
if (!apiKey)
|
|
97378
|
+
return "none configured";
|
|
97379
|
+
if (SECRET_STORE_REFERENCE_PATTERN.test(apiKey))
|
|
97380
|
+
return `${apiKey} (store)`;
|
|
97381
|
+
if (ENV_REFERENCE_PATTERN.test(apiKey))
|
|
97382
|
+
return `${apiKey} (env)`;
|
|
97383
|
+
return "literal apiKey";
|
|
97384
|
+
}
|
|
97178
97385
|
|
|
97179
97386
|
// src/llm/embedder.ts
|
|
97180
97387
|
var embedderOverrides;
|
|
@@ -97225,9 +97432,12 @@ function deriveSemanticProviderFingerprint(embedding) {
|
|
|
97225
97432
|
}
|
|
97226
97433
|
return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}`;
|
|
97227
97434
|
}
|
|
97228
|
-
|
|
97435
|
+
function formatEmbeddingHeartbeat(storedCount, total, failedCount) {
|
|
97436
|
+
return `Still generating embeddings: ${storedCount}/${total} stored, ${failedCount} failed; waiting on embedding provider.`;
|
|
97437
|
+
}
|
|
97229
97438
|
var CANARY_SAMPLE_SIZE = 8;
|
|
97230
97439
|
var CANARY_SIMILARITY_THRESHOLD = 0.999;
|
|
97440
|
+
var CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
97231
97441
|
function decideEmbeddingCompatibility(pairs) {
|
|
97232
97442
|
if (pairs.length === 0)
|
|
97233
97443
|
return { outcome: "keep", medianSimilarity: undefined, verifiedSamples: 0 };
|
|
@@ -97315,11 +97525,24 @@ function throwIfAborted(signal) {
|
|
|
97315
97525
|
}
|
|
97316
97526
|
}
|
|
97317
97527
|
async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds, opts) {
|
|
97528
|
+
if (db.inTransaction) {
|
|
97529
|
+
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.");
|
|
97530
|
+
}
|
|
97318
97531
|
throwIfAborted(signal);
|
|
97319
97532
|
if (config.semanticSearchMode === "off") {
|
|
97533
|
+
purgeEmbeddingSalvage(db);
|
|
97320
97534
|
onProgress({ phase: "embeddings", message: "Semantic search disabled; skipping embeddings." });
|
|
97321
97535
|
return { success: false, message: "Semantic search is disabled." };
|
|
97322
97536
|
}
|
|
97537
|
+
if (hasRemoteEndpoint(config.embedding ?? {})) {
|
|
97538
|
+
const endpoint = normalizeEmbeddingEndpoint(config.embedding?.endpoint ?? "");
|
|
97539
|
+
const credential = describeEmbeddingCredential(config.embedding?.apiKey);
|
|
97540
|
+
const configFileSuffix = isVerbose() ? `; config: ${getConfigPath()}` : "";
|
|
97541
|
+
onProgress({
|
|
97542
|
+
phase: "embeddings",
|
|
97543
|
+
message: `[embed] endpoint ${endpoint}, model ${config.embedding?.model ?? "unknown"}; credential: ${credential}${configFileSuffix}`
|
|
97544
|
+
});
|
|
97545
|
+
}
|
|
97323
97546
|
const vecFastPathWasReady = isVecFastPathReady(db);
|
|
97324
97547
|
const currentFingerprint = deriveSemanticProviderFingerprint(config.embedding);
|
|
97325
97548
|
const storedFingerprint = getMeta(db, "embeddingFingerprint");
|
|
@@ -97328,6 +97551,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97328
97551
|
if (opts?.forceReembed) {
|
|
97329
97552
|
db.transaction(() => {
|
|
97330
97553
|
purgeEmbeddings(db, { dropVecTable: true });
|
|
97554
|
+
purgeEmbeddingSalvage(db);
|
|
97331
97555
|
deleteMeta(db, "embeddingDim");
|
|
97332
97556
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97333
97557
|
deleteMeta(db, "embeddingIdentity");
|
|
@@ -97344,6 +97568,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97344
97568
|
if (decision.outcome === "rebuild") {
|
|
97345
97569
|
db.transaction(() => {
|
|
97346
97570
|
purgeEmbeddings(db, { dropVecTable: true });
|
|
97571
|
+
purgeEmbeddingSalvage(db);
|
|
97347
97572
|
deleteMeta(db, "embeddingDim");
|
|
97348
97573
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97349
97574
|
if (decision.identity)
|
|
@@ -97355,6 +97580,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97355
97580
|
rebuildReason = decision.reason;
|
|
97356
97581
|
} else {
|
|
97357
97582
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97583
|
+
relabelEmbeddingSalvageFingerprint(db, storedFingerprint, currentFingerprint);
|
|
97358
97584
|
if (decision.identity)
|
|
97359
97585
|
setMeta(db, "embeddingIdentity", decision.identity);
|
|
97360
97586
|
if (decision.verified) {
|
|
@@ -97365,28 +97591,66 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97365
97591
|
onProgress({ phase: "embeddings", message });
|
|
97366
97592
|
}
|
|
97367
97593
|
}
|
|
97594
|
+
} else {
|
|
97595
|
+
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97368
97596
|
}
|
|
97369
97597
|
try {
|
|
97370
97598
|
throwIfAborted(signal);
|
|
97371
97599
|
const allEntries = getAllEntriesForEmbedding(db, targetEntryIds);
|
|
97372
|
-
|
|
97600
|
+
let vecFailedCount = 0;
|
|
97601
|
+
let vecUnavailableCount = 0;
|
|
97602
|
+
const { reusedCount, remaining: candidateEntries } = reuseSalvagedEmbeddings(db, allEntries, currentFingerprint, (entry, embedding) => {
|
|
97603
|
+
const result = upsertEmbedding(db, entry.id, embedding);
|
|
97604
|
+
if (result.vec === "failed")
|
|
97605
|
+
vecFailedCount++;
|
|
97606
|
+
if (result.vec === "unavailable")
|
|
97607
|
+
vecUnavailableCount++;
|
|
97608
|
+
return result.stored;
|
|
97609
|
+
});
|
|
97610
|
+
if (reusedCount > 0) {
|
|
97611
|
+
onProgress({
|
|
97612
|
+
phase: "embeddings",
|
|
97613
|
+
message: `Reused ${reusedCount} embedding${reusedCount === 1 ? "" : "s"} from the previous generation; embedding ${candidateEntries.length} new.`
|
|
97614
|
+
});
|
|
97615
|
+
}
|
|
97616
|
+
if (candidateEntries.length === 0) {
|
|
97373
97617
|
onProgress({ phase: "embeddings", message: "Embeddings already up to date." });
|
|
97374
97618
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97375
|
-
|
|
97619
|
+
if (reusedCount > 0) {
|
|
97620
|
+
const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
|
|
97621
|
+
setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
|
|
97622
|
+
}
|
|
97623
|
+
purgeEmbeddingSalvage(db);
|
|
97624
|
+
return reusedCount > 0 ? { success: true, vecInsertFailures: vecFailedCount } : { success: true };
|
|
97625
|
+
}
|
|
97626
|
+
const maxInputTokens = config.embedding?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
97627
|
+
let truncatedCount = 0;
|
|
97628
|
+
const texts = [];
|
|
97629
|
+
const pendingEntries = [];
|
|
97630
|
+
for (const entry of candidateEntries) {
|
|
97631
|
+
const capped = capEmbeddingText(entry.searchText, maxInputTokens);
|
|
97632
|
+
if (capped.text.length === 0)
|
|
97633
|
+
continue;
|
|
97634
|
+
if (capped.truncated)
|
|
97635
|
+
truncatedCount++;
|
|
97636
|
+
pendingEntries.push(entry);
|
|
97637
|
+
texts.push(capped.text);
|
|
97638
|
+
}
|
|
97639
|
+
if (truncatedCount > 0) {
|
|
97640
|
+
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.`;
|
|
97641
|
+
onProgress({ phase: "embeddings", message });
|
|
97376
97642
|
}
|
|
97377
97643
|
if (rebuildReason) {
|
|
97378
|
-
const message = `[embed] Re-embedding ${
|
|
97379
|
-
warn(message);
|
|
97644
|
+
const message = `[embed] Re-embedding ${pendingEntries.length} entr${pendingEntries.length === 1 ? "y" : "ies"} because ${rebuildReason}`;
|
|
97380
97645
|
onProgress({ phase: "embeddings", message });
|
|
97381
97646
|
}
|
|
97382
97647
|
onProgress({
|
|
97383
97648
|
phase: "embeddings",
|
|
97384
|
-
message: `Generating embeddings for ${
|
|
97649
|
+
message: `Generating embeddings for ${pendingEntries.length} entr${pendingEntries.length === 1 ? "y" : "ies"}.`
|
|
97385
97650
|
});
|
|
97386
|
-
const texts = allEntries.map((entry) => entry.searchText);
|
|
97387
97651
|
if (isVerbose()) {
|
|
97388
97652
|
if (hasRemoteEndpoint(config.embedding ?? {})) {
|
|
97389
|
-
const tokenBudget = config.embedding?.maxTokens ??
|
|
97653
|
+
const tokenBudget = config.embedding?.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
97390
97654
|
const maxCount = config.embedding?.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
97391
97655
|
const batches = buildTokenBoundedBatches(texts, tokenBudget, maxCount);
|
|
97392
97656
|
const batchNumberByIndex = new Map;
|
|
@@ -97394,7 +97658,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97394
97658
|
for (const i of batch.indices)
|
|
97395
97659
|
batchNumberByIndex.set(i, batchIdx + 1);
|
|
97396
97660
|
});
|
|
97397
|
-
for (const [i, entry] of
|
|
97661
|
+
for (const [i, entry] of pendingEntries.entries()) {
|
|
97398
97662
|
const chars = entry.searchText.length;
|
|
97399
97663
|
const tokens = estimateTokenCount(entry.searchText);
|
|
97400
97664
|
const batch = batches[batchNumberByIndex.get(i) - 1];
|
|
@@ -97402,7 +97666,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97402
97666
|
warnVerbose(`[embed] ${entry.itemRef} (${chars} chars, est. ${tokens} tokens) → ${label}`);
|
|
97403
97667
|
}
|
|
97404
97668
|
} else {
|
|
97405
|
-
for (const entry of
|
|
97669
|
+
for (const entry of pendingEntries) {
|
|
97406
97670
|
warnVerbose(`[embed] ${entry.itemRef} (${entry.searchText.length} chars, est. ${estimateTokenCount(entry.searchText)} tokens)`);
|
|
97407
97671
|
}
|
|
97408
97672
|
}
|
|
@@ -97411,27 +97675,58 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97411
97675
|
let storedCount = 0;
|
|
97412
97676
|
let skippedCount = 0;
|
|
97413
97677
|
let embedFailedCount = 0;
|
|
97414
|
-
let vecFailedCount = 0;
|
|
97415
|
-
let vecUnavailableCount = 0;
|
|
97416
97678
|
let storedTokens = 0;
|
|
97417
|
-
let lastProgressBucket = 0;
|
|
97418
97679
|
try {
|
|
97419
97680
|
heartbeatTimer = setInterval(() => {
|
|
97420
97681
|
onProgress({
|
|
97421
97682
|
phase: "embeddings",
|
|
97422
|
-
message:
|
|
97683
|
+
message: formatEmbeddingHeartbeat(storedCount, pendingEntries.length, embedFailedCount)
|
|
97423
97684
|
});
|
|
97424
97685
|
}, 15000);
|
|
97425
97686
|
const skips = [];
|
|
97426
97687
|
const embedStart = Date.now();
|
|
97688
|
+
let consecutiveSingleDocFailures = 0;
|
|
97689
|
+
let consecutiveNetworkErrorFailures = 0;
|
|
97690
|
+
let circuitBreakerReason;
|
|
97691
|
+
const onSkip = (skip) => {
|
|
97692
|
+
skips.push(skip);
|
|
97693
|
+
if (!skip.batchStart)
|
|
97694
|
+
return;
|
|
97695
|
+
if (skip.reason === "context-window-exceeded") {
|
|
97696
|
+
consecutiveSingleDocFailures = 0;
|
|
97697
|
+
consecutiveNetworkErrorFailures = 0;
|
|
97698
|
+
return;
|
|
97699
|
+
}
|
|
97700
|
+
consecutiveSingleDocFailures = skip.batchSize === 1 ? consecutiveSingleDocFailures + 1 : 0;
|
|
97701
|
+
consecutiveNetworkErrorFailures = skip.failureKind === "network-error" ? consecutiveNetworkErrorFailures + 1 : 0;
|
|
97702
|
+
if (consecutiveSingleDocFailures >= CIRCUIT_BREAKER_THRESHOLD || consecutiveNetworkErrorFailures >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
97703
|
+
circuitBreakerReason = skip.message;
|
|
97704
|
+
return false;
|
|
97705
|
+
}
|
|
97706
|
+
return;
|
|
97707
|
+
};
|
|
97427
97708
|
let observedModel;
|
|
97428
97709
|
let observedVectorLen;
|
|
97429
|
-
const
|
|
97710
|
+
const reportPerBatchLine = hasRemoteEndpoint(config.embedding ?? {});
|
|
97711
|
+
const onBatch = (indices, batchEmbeddings, model, outcome) => {
|
|
97712
|
+
if (outcome?.outcome === "retrying") {
|
|
97713
|
+
if (reportPerBatchLine) {
|
|
97714
|
+
onProgress({
|
|
97715
|
+
phase: "embeddings",
|
|
97716
|
+
message: `[embed] batch ${outcome.batchIndex}/${outcome.batchCount}: ${outcome.docCount} docs, ${outcome.requestTokens.toLocaleString()} tokens → retrying after ${(outcome.elapsedMs / 1000).toFixed(1)} s`
|
|
97717
|
+
});
|
|
97718
|
+
}
|
|
97719
|
+
return;
|
|
97720
|
+
}
|
|
97430
97721
|
if (model)
|
|
97431
97722
|
observedModel = model;
|
|
97723
|
+
if (batchEmbeddings.some((embedding) => embedding !== undefined)) {
|
|
97724
|
+
consecutiveSingleDocFailures = 0;
|
|
97725
|
+
consecutiveNetworkErrorFailures = 0;
|
|
97726
|
+
}
|
|
97432
97727
|
db.transaction(() => {
|
|
97433
97728
|
for (let k2 = 0;k2 < indices.length; k2++) {
|
|
97434
|
-
const entry =
|
|
97729
|
+
const entry = pendingEntries[indices[k2]];
|
|
97435
97730
|
if (!entry)
|
|
97436
97731
|
continue;
|
|
97437
97732
|
const embedding = batchEmbeddings[k2];
|
|
@@ -97454,29 +97749,25 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97454
97749
|
vecUnavailableCount++;
|
|
97455
97750
|
}
|
|
97456
97751
|
})();
|
|
97457
|
-
|
|
97458
|
-
|
|
97459
|
-
|
|
97752
|
+
if (outcome && outcome.reason !== "oversized" && reportPerBatchLine) {
|
|
97753
|
+
const elapsedSeconds2 = (outcome.elapsedMs / 1000).toFixed(1);
|
|
97754
|
+
const outcomeLabel = outcome.outcome === "stored" ? `${outcome.docCount} stored (${elapsedSeconds2} s)` : `failed: ${outcome.reason}`;
|
|
97460
97755
|
onProgress({
|
|
97461
97756
|
phase: "embeddings",
|
|
97462
|
-
message: `
|
|
97757
|
+
message: `[embed] batch ${outcome.batchIndex}/${outcome.batchCount}: ${outcome.docCount} docs, ${outcome.requestTokens.toLocaleString()} tokens → ${outcomeLabel}`
|
|
97463
97758
|
});
|
|
97464
97759
|
}
|
|
97760
|
+
onProgress({
|
|
97761
|
+
phase: "embeddings",
|
|
97762
|
+
message: `Embedded ${storedCount}/${pendingEntries.length} entries.`
|
|
97763
|
+
});
|
|
97465
97764
|
};
|
|
97466
|
-
await embedBatch(texts, config.embedding, signal,
|
|
97765
|
+
await embedBatch(texts, config.embedding, signal, onSkip, onBatch);
|
|
97467
97766
|
throwIfAborted(signal);
|
|
97468
97767
|
const elapsedSeconds = Math.max((Date.now() - embedStart) / 1000, 0.001);
|
|
97469
97768
|
if (skippedCount > 0) {
|
|
97470
97769
|
warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
|
|
97471
97770
|
}
|
|
97472
|
-
if (embedFailedCount > 0) {
|
|
97473
|
-
const detail = skips.slice(0, 20).map((skip) => ` - ${allEntries[skip.index]?.itemRef ?? skip.index} (${skip.reason}): ${skip.message}`).join(`
|
|
97474
|
-
`);
|
|
97475
|
-
const more = skips.length > 20 ? `
|
|
97476
|
-
...and ${skips.length - 20} more` : "";
|
|
97477
|
-
warn(`[embed] ${embedFailedCount} embedding${embedFailedCount === 1 ? "" : "s"} could not be generated and ${embedFailedCount === 1 ? "was" : "were"} skipped:
|
|
97478
|
-
${detail}${more}`);
|
|
97479
|
-
}
|
|
97480
97771
|
const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
|
|
97481
97772
|
setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
|
|
97482
97773
|
if (vecFailedCount > 0) {
|
|
@@ -97484,14 +97775,39 @@ ${detail}${more}`);
|
|
|
97484
97775
|
}
|
|
97485
97776
|
const entriesPerSec = storedCount / elapsedSeconds;
|
|
97486
97777
|
const tokensPerSec = storedTokens / elapsedSeconds;
|
|
97778
|
+
const totalStored = storedCount + reusedCount;
|
|
97779
|
+
const oversizedSkips = skips.filter((skip) => skip.reason === "context-window-exceeded");
|
|
97780
|
+
const timedOutSkips = skips.filter((skip) => skip.reason === "batch-request-failed" && skip.failureKind === "timeout");
|
|
97781
|
+
const failedSkips = skips.filter((skip) => skip.reason === "batch-request-failed" && skip.failureKind !== "timeout");
|
|
97782
|
+
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)`;
|
|
97487
97783
|
onProgress({
|
|
97488
97784
|
phase: "embeddings",
|
|
97489
|
-
message:
|
|
97785
|
+
message: `${throughputLine}; ${oversizedSkips.length} oversized skipped, ${timedOutSkips.length} timed out, ${failedSkips.length} failed.`
|
|
97490
97786
|
});
|
|
97787
|
+
const printSkipList = (label, skipList) => {
|
|
97788
|
+
if (skipList.length === 0)
|
|
97789
|
+
return;
|
|
97790
|
+
const limit = isVerbose() ? skipList.length : 20;
|
|
97791
|
+
const listed = skipList.slice(0, limit).map((skip) => ` - ${pendingEntries[skip.index]?.itemRef ?? skip.index}: ${skip.message}`).join(`
|
|
97792
|
+
`);
|
|
97793
|
+
const more = skipList.length > limit ? `
|
|
97794
|
+
...and ${skipList.length - limit} more` : "";
|
|
97795
|
+
onProgress({ phase: "embeddings", message: `[embed] ${label} skipped:
|
|
97796
|
+
${listed}${more}` });
|
|
97797
|
+
};
|
|
97798
|
+
printSkipList("oversized documents", oversizedSkips);
|
|
97799
|
+
printSkipList("timed-out documents", timedOutSkips);
|
|
97800
|
+
printSkipList("failed documents", failedSkips);
|
|
97491
97801
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97492
97802
|
const observedIdentity = deriveObservedEmbeddingIdentity(config.embedding, observedModel, observedVectorLen);
|
|
97493
97803
|
if (observedIdentity)
|
|
97494
97804
|
setMeta(db, "embeddingIdentity", observedIdentity);
|
|
97805
|
+
if (circuitBreakerReason !== undefined) {
|
|
97806
|
+
const message = `embedding provider failed ${CIRCUIT_BREAKER_THRESHOLD} consecutive batches ` + `(last: ${circuitBreakerReason}); stopped after ${storedCount} embedding${storedCount === 1 ? "" : "s"} ` + "were stored — rerun akm index when the endpoint is healthy";
|
|
97807
|
+
warn(`[embed] ${message}`);
|
|
97808
|
+
onProgress({ phase: "embeddings", message });
|
|
97809
|
+
return { success: false, message, vecInsertFailures: vecFailedCount };
|
|
97810
|
+
}
|
|
97495
97811
|
if (storedCount === 0 && embedFailedCount > 0) {
|
|
97496
97812
|
const firstMessage = skips[0]?.message ?? "All embeddings failed.";
|
|
97497
97813
|
return {
|
|
@@ -97499,6 +97815,7 @@ ${detail}${more}`);
|
|
|
97499
97815
|
message: `All ${embedFailedCount} embedding batch(es) failed: ${firstMessage}`
|
|
97500
97816
|
};
|
|
97501
97817
|
}
|
|
97818
|
+
purgeEmbeddingSalvage(db);
|
|
97502
97819
|
return { success: true, vecInsertFailures: vecFailedCount };
|
|
97503
97820
|
} finally {
|
|
97504
97821
|
if (heartbeatTimer)
|
|
@@ -97915,7 +98232,11 @@ async function indexWrittenAssets(stashDir, filePaths, options = {}) {
|
|
|
97915
98232
|
return await (async () => {
|
|
97916
98233
|
const rebuildProbe = probeLock(getIndexRebuildLockPath());
|
|
97917
98234
|
if (rebuildProbe.state === "held") {
|
|
97918
|
-
|
|
98235
|
+
const holderLabel = formatLockHolderPid({
|
|
98236
|
+
pid: rebuildProbe.holderPid,
|
|
98237
|
+
launcherPid: rebuildProbe.launcherPid ?? null
|
|
98238
|
+
});
|
|
98239
|
+
warn(`index rebuild in progress (pid ${holderLabel}); the next index pass will index ${filePaths.join(", ")}`);
|
|
97919
98240
|
return true;
|
|
97920
98241
|
}
|
|
97921
98242
|
const dbPath = getDbPath();
|
|
@@ -98889,7 +99210,7 @@ var PROPOSAL_TXN_PHASES = [
|
|
|
98889
99210
|
"committed"
|
|
98890
99211
|
];
|
|
98891
99212
|
function proposalHash(content) {
|
|
98892
|
-
return
|
|
99213
|
+
return createHash11("sha256").update(content).digest("hex");
|
|
98893
99214
|
}
|
|
98894
99215
|
function proposalFileHash(filePath) {
|
|
98895
99216
|
return proposalHash(fs55.readFileSync(filePath));
|