sensemaking 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -9
- package/dist/cjs/cli/index.d.cts +1 -1
- package/dist/cjs/cli/index.d.ts +1 -1
- package/dist/cjs/cli/index.js +1 -1
- package/dist/cjs/cli/index.js.map +1 -1
- package/dist/cjs/cli/path.js +5 -29
- package/dist/cjs/cli/path.js.map +1 -1
- package/dist/cjs/cli/related.js +4 -3
- package/dist/cjs/cli/related.js.map +1 -1
- package/dist/cjs/commands/index.d.cts +6 -3
- package/dist/cjs/commands/index.d.ts +6 -3
- package/dist/cjs/commands/index.js +4 -0
- package/dist/cjs/commands/index.js.map +1 -1
- package/dist/cjs/commands/map.d.cts +14 -5
- package/dist/cjs/commands/map.d.ts +14 -5
- package/dist/cjs/commands/map.js +42 -35
- package/dist/cjs/commands/map.js.map +1 -1
- package/dist/cjs/commands/path.d.cts +14 -0
- package/dist/cjs/commands/path.d.ts +14 -0
- package/dist/cjs/commands/path.js +209 -0
- package/dist/cjs/commands/path.js.map +1 -0
- package/dist/cjs/commands/peek.d.cts +16 -1
- package/dist/cjs/commands/peek.d.ts +16 -1
- package/dist/cjs/commands/peek.js +71 -33
- package/dist/cjs/commands/peek.js.map +1 -1
- package/dist/cjs/commands/related.d.cts +16 -3
- package/dist/cjs/commands/related.d.ts +16 -3
- package/dist/cjs/commands/related.js +131 -98
- package/dist/cjs/commands/related.js.map +1 -1
- package/dist/cjs/commands/scope.d.cts +1 -0
- package/dist/cjs/commands/scope.d.ts +1 -0
- package/dist/cjs/commands/scope.js +149 -0
- package/dist/cjs/commands/scope.js.map +1 -1
- package/dist/cjs/commands/search.d.cts +26 -3
- package/dist/cjs/commands/search.d.ts +26 -3
- package/dist/cjs/commands/search.js +211 -71
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/commands/signals.d.cts +4 -3
- package/dist/cjs/commands/signals.d.ts +4 -3
- package/dist/cjs/commands/signals.js +13 -4
- package/dist/cjs/commands/signals.js.map +1 -1
- package/dist/cjs/commands/snapshot.d.cts +3 -0
- package/dist/cjs/commands/snapshot.d.ts +3 -0
- package/dist/cjs/commands/snapshot.js +180 -0
- package/dist/cjs/commands/snapshot.js.map +1 -0
- package/dist/cjs/embed/cohere.js +41 -16
- package/dist/cjs/embed/cohere.js.map +1 -1
- package/dist/cjs/embed/endpoint.d.cts +2 -0
- package/dist/cjs/embed/endpoint.d.ts +2 -0
- package/dist/cjs/embed/endpoint.js +30 -0
- package/dist/cjs/embed/endpoint.js.map +1 -0
- package/dist/cjs/embed/handoff.d.cts +7 -2
- package/dist/cjs/embed/handoff.d.ts +7 -2
- package/dist/cjs/embed/handoff.js +10 -0
- package/dist/cjs/embed/handoff.js.map +1 -1
- package/dist/cjs/embed/http.d.cts +18 -1
- package/dist/cjs/embed/http.d.ts +18 -1
- package/dist/cjs/embed/http.js +239 -40
- package/dist/cjs/embed/http.js.map +1 -1
- package/dist/cjs/embed/openai.js +39 -17
- package/dist/cjs/embed/openai.js.map +1 -1
- package/dist/cjs/embed/query.d.cts +9 -3
- package/dist/cjs/embed/query.d.ts +9 -3
- package/dist/cjs/embed/query.js +257 -90
- package/dist/cjs/embed/query.js.map +1 -1
- package/dist/cjs/embed/registry.d.cts +2 -2
- package/dist/cjs/embed/registry.d.ts +2 -2
- package/dist/cjs/embed/registry.js +36 -4
- package/dist/cjs/embed/registry.js.map +1 -1
- package/dist/cjs/embed/static.js +6 -2
- package/dist/cjs/embed/static.js.map +1 -1
- package/dist/cjs/embed/store.js +42 -77
- package/dist/cjs/embed/store.js.map +1 -1
- package/dist/cjs/embed/types.d.cts +5 -2
- package/dist/cjs/embed/types.d.ts +5 -2
- package/dist/cjs/embed/types.js.map +1 -1
- package/dist/cjs/features/embed.js +290 -43
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/graph/traverse.d.cts +2 -2
- package/dist/cjs/graph/traverse.d.ts +2 -2
- package/dist/cjs/graph/traverse.js +15 -5
- package/dist/cjs/graph/traverse.js.map +1 -1
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output/output.d.cts +4 -36
- package/dist/cjs/output/output.d.ts +4 -36
- package/dist/cjs/output/output.js +1 -2
- package/dist/cjs/output/output.js.map +1 -1
- package/dist/cjs/scan/pool.d.cts +2 -1
- package/dist/cjs/scan/pool.d.ts +2 -1
- package/dist/cjs/scan/pool.js +116 -27
- package/dist/cjs/scan/pool.js.map +1 -1
- package/dist/cjs/scan/reparse.d.cts +1 -0
- package/dist/cjs/scan/reparse.d.ts +1 -0
- package/dist/cjs/scan/reparse.js +75 -21
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/store/builder.d.cts +11 -21
- package/dist/cjs/store/builder.d.ts +11 -21
- package/dist/cjs/store/builder.js +1077 -438
- package/dist/cjs/store/builder.js.map +1 -1
- package/dist/cjs/store/duckdb/connection.js +16 -2
- package/dist/cjs/store/duckdb/connection.js.map +1 -1
- package/dist/cjs/store/duckdb/open.d.cts +1 -1
- package/dist/cjs/store/duckdb/open.d.ts +1 -1
- package/dist/cjs/store/duckdb/open.js +73 -65
- package/dist/cjs/store/duckdb/open.js.map +1 -1
- package/dist/cjs/store/duckdb/reconcile.js +533 -15
- package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
- package/dist/cjs/store/embed-scope.js +19 -9
- package/dist/cjs/store/embed-scope.js.map +1 -1
- package/dist/cjs/store/index.d.cts +6 -0
- package/dist/cjs/store/index.d.ts +6 -0
- package/dist/cjs/store/index.js +8 -4
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/open.d.cts +6 -1
- package/dist/cjs/store/open.d.ts +6 -1
- package/dist/cjs/store/open.js +230 -307
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/readiness.d.cts +5 -0
- package/dist/cjs/store/readiness.d.ts +5 -0
- package/dist/cjs/store/readiness.js +39 -0
- package/dist/cjs/store/readiness.js.map +1 -0
- package/dist/cjs/store/reconcile.d.cts +26 -1
- package/dist/cjs/store/reconcile.d.ts +26 -1
- package/dist/cjs/store/reconcile.js +670 -576
- package/dist/cjs/store/reconcile.js.map +1 -1
- package/dist/cjs/store/signature.d.cts +1 -0
- package/dist/cjs/store/signature.d.ts +1 -0
- package/dist/cjs/store/signature.js +9 -0
- package/dist/cjs/store/signature.js.map +1 -1
- package/dist/cjs/store/sqlite/open.d.cts +1 -1
- package/dist/cjs/store/sqlite/open.d.ts +1 -1
- package/dist/cjs/store/sqlite/open.js +84 -90
- package/dist/cjs/store/sqlite/open.js.map +1 -1
- package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
- package/dist/cjs/store/sqlite/store.js +63 -60
- package/dist/cjs/store/sqlite/store.js.map +1 -1
- package/dist/cjs/store/sqlite/vectors.d.cts +2 -2
- package/dist/cjs/store/sqlite/vectors.d.ts +2 -2
- package/dist/cjs/store/sqlite/vectors.js +8 -4
- package/dist/cjs/store/sqlite/vectors.js.map +1 -1
- package/dist/cjs/store/stages.js.map +1 -1
- package/dist/cjs/store/turso/connection.d.cts +1 -0
- package/dist/cjs/store/turso/connection.d.ts +1 -0
- package/dist/cjs/store/turso/connection.js +319 -94
- package/dist/cjs/store/turso/connection.js.map +1 -1
- package/dist/cjs/store/turso/open.d.cts +1 -1
- package/dist/cjs/store/turso/open.d.ts +1 -1
- package/dist/cjs/store/turso/open.js +70 -74
- package/dist/cjs/store/turso/open.js.map +1 -1
- package/dist/cjs/store/turso/store.js +25 -23
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/turso/vectors.d.cts +2 -2
- package/dist/cjs/store/turso/vectors.d.ts +2 -2
- package/dist/cjs/store/turso/vectors.js +26 -16
- package/dist/cjs/store/turso/vectors.js.map +1 -1
- package/dist/cjs/store/types.d.cts +4 -1
- package/dist/cjs/store/types.d.ts +4 -1
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/store/vectors.d.cts +14 -1
- package/dist/cjs/store/vectors.d.ts +14 -1
- package/dist/cjs/store/vectors.js +61 -0
- package/dist/cjs/store/vectors.js.map +1 -1
- package/dist/cjs/watch.d.cts +6 -0
- package/dist/cjs/watch.d.ts +6 -0
- package/dist/cjs/watch.js +42 -10
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.d.cts +4 -3
- package/dist/cjs/workers/parse.d.ts +4 -3
- package/dist/cjs/workers/parse.js +19 -17
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/esm/cli/index.d.ts +1 -1
- package/dist/esm/cli/index.js +1 -1
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/cli/path.js +4 -10
- package/dist/esm/cli/path.js.map +1 -1
- package/dist/esm/cli/related.js +5 -3
- package/dist/esm/cli/related.js.map +1 -1
- package/dist/esm/commands/index.d.ts +6 -3
- package/dist/esm/commands/index.js +1 -0
- package/dist/esm/commands/index.js.map +1 -1
- package/dist/esm/commands/map.d.ts +14 -5
- package/dist/esm/commands/map.js +2 -0
- package/dist/esm/commands/map.js.map +1 -1
- package/dist/esm/commands/path.d.ts +14 -0
- package/dist/esm/commands/path.js +33 -0
- package/dist/esm/commands/path.js.map +1 -0
- package/dist/esm/commands/peek.d.ts +16 -1
- package/dist/esm/commands/peek.js +35 -9
- package/dist/esm/commands/peek.js.map +1 -1
- package/dist/esm/commands/related.d.ts +16 -3
- package/dist/esm/commands/related.js +45 -28
- package/dist/esm/commands/related.js.map +1 -1
- package/dist/esm/commands/scope.d.ts +1 -0
- package/dist/esm/commands/scope.js +58 -0
- package/dist/esm/commands/scope.js.map +1 -1
- package/dist/esm/commands/search.d.ts +26 -3
- package/dist/esm/commands/search.js +85 -18
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/commands/signals.d.ts +4 -3
- package/dist/esm/commands/signals.js +11 -4
- package/dist/esm/commands/signals.js.map +1 -1
- package/dist/esm/commands/snapshot.d.ts +3 -0
- package/dist/esm/commands/snapshot.js +25 -0
- package/dist/esm/commands/snapshot.js.map +1 -0
- package/dist/esm/embed/cohere.js +35 -10
- package/dist/esm/embed/cohere.js.map +1 -1
- package/dist/esm/embed/endpoint.d.ts +2 -0
- package/dist/esm/embed/endpoint.js +11 -0
- package/dist/esm/embed/endpoint.js.map +1 -0
- package/dist/esm/embed/handoff.d.ts +7 -2
- package/dist/esm/embed/handoff.js +7 -0
- package/dist/esm/embed/handoff.js.map +1 -1
- package/dist/esm/embed/http.d.ts +18 -1
- package/dist/esm/embed/http.js +137 -17
- package/dist/esm/embed/http.js.map +1 -1
- package/dist/esm/embed/openai.js +34 -12
- package/dist/esm/embed/openai.js.map +1 -1
- package/dist/esm/embed/query.d.ts +9 -3
- package/dist/esm/embed/query.js +98 -26
- package/dist/esm/embed/query.js.map +1 -1
- package/dist/esm/embed/registry.d.ts +2 -2
- package/dist/esm/embed/registry.js +34 -5
- package/dist/esm/embed/registry.js.map +1 -1
- package/dist/esm/embed/static.js +10 -2
- package/dist/esm/embed/static.js.map +1 -1
- package/dist/esm/embed/store.js +30 -21
- package/dist/esm/embed/store.js.map +1 -1
- package/dist/esm/embed/types.d.ts +5 -2
- package/dist/esm/embed/types.js.map +1 -1
- package/dist/esm/features/embed.js +82 -21
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/graph/traverse.d.ts +2 -2
- package/dist/esm/graph/traverse.js +4 -2
- package/dist/esm/graph/traverse.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output/output.d.ts +4 -36
- package/dist/esm/output/output.js +1 -2
- package/dist/esm/output/output.js.map +1 -1
- package/dist/esm/scan/pool.d.ts +2 -1
- package/dist/esm/scan/pool.js +80 -23
- package/dist/esm/scan/pool.js.map +1 -1
- package/dist/esm/scan/reparse.d.ts +1 -0
- package/dist/esm/scan/reparse.js +40 -12
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/store/builder.d.ts +11 -21
- package/dist/esm/store/builder.js +337 -133
- package/dist/esm/store/builder.js.map +1 -1
- package/dist/esm/store/duckdb/connection.js +9 -1
- package/dist/esm/store/duckdb/connection.js.map +1 -1
- package/dist/esm/store/duckdb/open.d.ts +1 -1
- package/dist/esm/store/duckdb/open.js +13 -9
- package/dist/esm/store/duckdb/open.js.map +1 -1
- package/dist/esm/store/duckdb/reconcile.js +145 -10
- package/dist/esm/store/duckdb/reconcile.js.map +1 -1
- package/dist/esm/store/embed-scope.js +16 -6
- package/dist/esm/store/embed-scope.js.map +1 -1
- package/dist/esm/store/index.d.ts +6 -0
- package/dist/esm/store/index.js +10 -4
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/open.d.ts +6 -1
- package/dist/esm/store/open.js +77 -87
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/readiness.d.ts +5 -0
- package/dist/esm/store/readiness.js +11 -0
- package/dist/esm/store/readiness.js.map +1 -0
- package/dist/esm/store/reconcile.d.ts +26 -1
- package/dist/esm/store/reconcile.js +142 -120
- package/dist/esm/store/reconcile.js.map +1 -1
- package/dist/esm/store/signature.d.ts +1 -0
- package/dist/esm/store/signature.js +8 -0
- package/dist/esm/store/signature.js.map +1 -1
- package/dist/esm/store/sqlite/open.d.ts +1 -1
- package/dist/esm/store/sqlite/open.js +12 -12
- package/dist/esm/store/sqlite/open.js.map +1 -1
- package/dist/esm/store/sqlite/reconcile.js +2 -2
- package/dist/esm/store/sqlite/reconcile.js.map +1 -1
- package/dist/esm/store/sqlite/store.js +21 -18
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/sqlite/vectors.d.ts +2 -2
- package/dist/esm/store/sqlite/vectors.js +7 -5
- package/dist/esm/store/sqlite/vectors.js.map +1 -1
- package/dist/esm/store/stages.js.map +1 -1
- package/dist/esm/store/turso/connection.d.ts +1 -0
- package/dist/esm/store/turso/connection.js +97 -28
- package/dist/esm/store/turso/connection.js.map +1 -1
- package/dist/esm/store/turso/open.d.ts +1 -1
- package/dist/esm/store/turso/open.js +9 -8
- package/dist/esm/store/turso/open.js.map +1 -1
- package/dist/esm/store/turso/store.js +16 -14
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/turso/vectors.d.ts +2 -2
- package/dist/esm/store/turso/vectors.js +27 -17
- package/dist/esm/store/turso/vectors.js.map +1 -1
- package/dist/esm/store/types.d.ts +4 -1
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/store/vectors.d.ts +14 -1
- package/dist/esm/store/vectors.js +55 -0
- package/dist/esm/store/vectors.js.map +1 -1
- package/dist/esm/watch.d.ts +6 -0
- package/dist/esm/watch.js +40 -14
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.d.ts +4 -3
- package/dist/esm/workers/parse.js +19 -17
- package/dist/esm/workers/parse.js.map +1 -1
- package/package.json +1 -1
- package/skills/sense/SKILL.md +4 -2
- package/skills/sense/references/stores/duckdb.md +1 -1
- package/skills/sense-setup/SKILL.md +2 -2
- package/skills/sense-setup/references/embeddings.md +1 -1
- package/skills/sense-setup/references/store-benchmarks.md +5 -5
- package/skills/sense-setup/references/stores/duckdb.md +1 -1
package/dist/esm/embed/http.js
CHANGED
|
@@ -1,33 +1,153 @@
|
|
|
1
|
+
import { SenseError } from '../errors.js';
|
|
1
2
|
const MAX_ATTEMPTS = 3;
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
const MAX_RETRY_AFTER_MS = 10000;
|
|
4
|
+
const DEFAULT_BASE_DELAY_MS = 200;
|
|
5
|
+
// Initial policy bounds small API/metadata responses separately from model-file downloads.
|
|
6
|
+
export const HTTP_ATTEMPT_TIMEOUT_MS = 30000;
|
|
7
|
+
export const MODEL_FILE_ATTEMPT_TIMEOUT_MS = 5 * 60000;
|
|
8
|
+
function safeUrl(url) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(url);
|
|
11
|
+
parsed.username = '';
|
|
12
|
+
parsed.password = '';
|
|
13
|
+
parsed.search = '';
|
|
14
|
+
parsed.hash = '';
|
|
15
|
+
return parsed.href;
|
|
16
|
+
} catch {
|
|
17
|
+
return 'the configured embedding endpoint';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function attemptController(callerSignal, timeoutMs) {
|
|
21
|
+
callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.throwIfAborted();
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeoutReason = {};
|
|
24
|
+
const relayAbort = ()=>controller.abort(callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.reason);
|
|
25
|
+
callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.addEventListener('abort', relayAbort, {
|
|
26
|
+
once: true
|
|
27
|
+
});
|
|
28
|
+
if (callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.aborted) relayAbort();
|
|
29
|
+
const timer = setTimeout(()=>controller.abort(timeoutReason), timeoutMs);
|
|
30
|
+
return {
|
|
31
|
+
controller,
|
|
32
|
+
timeoutReason,
|
|
33
|
+
cleanup: ()=>{
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.removeEventListener('abort', relayAbort);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function disposeResponse(response, controller) {
|
|
40
|
+
const cancellation = response.body && !response.bodyUsed ? response.body.cancel() : undefined;
|
|
41
|
+
controller.abort();
|
|
42
|
+
// The controller owns socket cleanup; observe stream cancellation without delaying the attempt.
|
|
43
|
+
if (cancellation) void cancellation.then(()=>undefined, ()=>undefined);
|
|
44
|
+
}
|
|
45
|
+
function sleep(ms, signal) {
|
|
46
|
+
signal === null || signal === void 0 ? void 0 : signal.throwIfAborted();
|
|
47
|
+
if (!signal) return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
48
|
+
return new Promise((resolve, reject)=>{
|
|
49
|
+
let timer;
|
|
50
|
+
const cleanup = ()=>{
|
|
51
|
+
if (timer) clearTimeout(timer);
|
|
52
|
+
signal.removeEventListener('abort', onAbort);
|
|
53
|
+
};
|
|
54
|
+
const onAbort = ()=>{
|
|
55
|
+
cleanup();
|
|
56
|
+
reject(signal.reason);
|
|
57
|
+
};
|
|
58
|
+
signal.addEventListener('abort', onAbort, {
|
|
59
|
+
once: true
|
|
60
|
+
});
|
|
61
|
+
timer = setTimeout(()=>{
|
|
62
|
+
cleanup();
|
|
63
|
+
resolve();
|
|
64
|
+
}, ms);
|
|
65
|
+
if (signal.aborted) onAbort();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function failedRequest(url, phase, attempts, timeoutMs, timedOut) {
|
|
69
|
+
const endpoint = safeUrl(url);
|
|
70
|
+
if (timedOut) {
|
|
71
|
+
return new SenseError('EMBED_MODEL', `${endpoint} timed out after ${timeoutMs}ms while waiting for ${phase} (attempt ${attempts}/${MAX_ATTEMPTS}); check the embedding service and network`);
|
|
72
|
+
}
|
|
73
|
+
return new SenseError('EMBED_MODEL', `${endpoint} failed while waiting for ${phase} after ${attempts} attempts; check the embedding service and network`);
|
|
74
|
+
}
|
|
75
|
+
function buffered(response, body, requestUrl) {
|
|
76
|
+
return {
|
|
77
|
+
ok: response.ok,
|
|
78
|
+
status: response.status,
|
|
79
|
+
headers: response.headers,
|
|
80
|
+
body,
|
|
81
|
+
url: safeUrl(response.url || requestUrl)
|
|
82
|
+
};
|
|
4
83
|
}
|
|
5
|
-
//
|
|
6
|
-
// backoff
|
|
7
|
-
export async function fetchWithRetry(url, init,
|
|
84
|
+
// Each attempt covers response headers and the complete successful body. Retry cleanup and
|
|
85
|
+
// backoff honor one caller signal without transferring ownership of that signal.
|
|
86
|
+
export async function fetchWithRetry(url, init, options = {}) {
|
|
87
|
+
var _options_attemptTimeoutMs, _options_baseDelayMs;
|
|
88
|
+
const timeoutMs = (_options_attemptTimeoutMs = options.attemptTimeoutMs) !== null && _options_attemptTimeoutMs !== void 0 ? _options_attemptTimeoutMs : HTTP_ATTEMPT_TIMEOUT_MS;
|
|
89
|
+
const baseDelayMs = (_options_baseDelayMs = options.baseDelayMs) !== null && _options_baseDelayMs !== void 0 ? _options_baseDelayMs : DEFAULT_BASE_DELAY_MS;
|
|
8
90
|
for(let attempt = 0; attempt < MAX_ATTEMPTS; attempt++){
|
|
91
|
+
var _options_signal;
|
|
92
|
+
(_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
|
|
93
|
+
const { controller, timeoutReason, cleanup } = attemptController(options.signal, timeoutMs);
|
|
94
|
+
let response;
|
|
95
|
+
let phase = 'response headers';
|
|
96
|
+
let retryDelayMs;
|
|
9
97
|
try {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
98
|
+
response = await fetch(url, {
|
|
99
|
+
...init,
|
|
100
|
+
signal: controller.signal
|
|
101
|
+
});
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const retryable = response.status === 429 || response.status >= 500;
|
|
104
|
+
if (retryable && attempt < MAX_ATTEMPTS - 1) {
|
|
105
|
+
const retryAfter = Number(response.headers.get('retry-after'));
|
|
106
|
+
retryDelayMs = retryAfter > 0 ? Math.min(retryAfter * 1000, MAX_RETRY_AFTER_MS) : baseDelayMs * 2 ** attempt;
|
|
107
|
+
disposeResponse(response, controller);
|
|
108
|
+
} else {
|
|
109
|
+
var _options_signal1;
|
|
110
|
+
disposeResponse(response, controller);
|
|
111
|
+
(_options_signal1 = options.signal) === null || _options_signal1 === void 0 ? void 0 : _options_signal1.throwIfAborted();
|
|
112
|
+
return buffered(response, new ArrayBuffer(0), url);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
var _options_signal2;
|
|
116
|
+
phase = 'response body';
|
|
117
|
+
const body = await response.arrayBuffer();
|
|
118
|
+
(_options_signal2 = options.signal) === null || _options_signal2 === void 0 ? void 0 : _options_signal2.throwIfAborted();
|
|
119
|
+
return buffered(response, body, url);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
try {
|
|
123
|
+
var _options_signal3;
|
|
124
|
+
(_options_signal3 = options.signal) === null || _options_signal3 === void 0 ? void 0 : _options_signal3.throwIfAborted();
|
|
125
|
+
} finally{
|
|
126
|
+
if (response) disposeResponse(response, controller);
|
|
127
|
+
}
|
|
128
|
+
const timedOut = controller.signal.reason === timeoutReason;
|
|
129
|
+
if (attempt === MAX_ATTEMPTS - 1) throw failedRequest(url, phase, attempt + 1, timeoutMs, timedOut);
|
|
130
|
+
retryDelayMs = baseDelayMs * 2 ** attempt;
|
|
131
|
+
} finally{
|
|
132
|
+
cleanup();
|
|
18
133
|
}
|
|
134
|
+
await sleep(retryDelayMs !== null && retryDelayMs !== void 0 ? retryDelayMs : baseDelayMs * 2 ** attempt, options.signal);
|
|
19
135
|
}
|
|
20
136
|
throw new Error('unreachable');
|
|
21
137
|
}
|
|
22
|
-
// Any HTTP response means the endpoint is up
|
|
23
|
-
//
|
|
138
|
+
// Any HTTP response means the endpoint is up. Abort it immediately because status does not
|
|
139
|
+
// consume its body, and bound the headers wait for an air-gapped machine.
|
|
24
140
|
export async function probeReachable(url, timeoutMs = 1500) {
|
|
141
|
+
const { controller, cleanup } = attemptController(undefined, timeoutMs);
|
|
25
142
|
try {
|
|
26
|
-
await fetch(url, {
|
|
27
|
-
signal:
|
|
143
|
+
const response = await fetch(url, {
|
|
144
|
+
signal: controller.signal
|
|
28
145
|
});
|
|
146
|
+
disposeResponse(response, controller);
|
|
29
147
|
return true;
|
|
30
148
|
} catch {
|
|
31
149
|
return false;
|
|
150
|
+
} finally{
|
|
151
|
+
cleanup();
|
|
32
152
|
}
|
|
33
153
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/http.ts"],"sourcesContent":["const MAX_ATTEMPTS = 3;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// Shared retry glue for the HTTP providers: 429/5xx/network failure retry with exponential\n// backoff honoring Retry-After; any other status returns immediately for the caller to handle.\nexport async function fetchWithRetry(url: string, init: RequestInit, baseDelayMs = 200): Promise<Response> {\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n try {\n const res = await fetch(url, init);\n if (res.ok || (res.status !== 429 && res.status < 500) || attempt === MAX_ATTEMPTS - 1) return res;\n const retryAfter = Number(res.headers.get('retry-after'));\n // Capped: a server naming a huge Retry-After must not stall an embed run for it.\n await sleep(retryAfter > 0 ? Math.min(retryAfter * 1000, 10_000) : baseDelayMs * 2 ** attempt);\n } catch (err) {\n if (attempt === MAX_ATTEMPTS - 1) throw err;\n await sleep(baseDelayMs * 2 ** attempt);\n }\n }\n throw new Error('unreachable');\n}\n\n// Any HTTP response means the endpoint is up; only a network failure or the timeout counts as\n// unreachable, so an air-gapped machine never waits more than timeoutMs on `sense status`.\nexport async function probeReachable(url: string, timeoutMs = 1500): Promise<boolean> {\n try {\n await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });\n return true;\n } catch {\n return false;\n }\n}\n"],"names":["MAX_ATTEMPTS","sleep","ms","Promise","resolve","setTimeout","fetchWithRetry","url","init","baseDelayMs","attempt","res","fetch","ok","status","retryAfter","Number","headers","get","Math","min","err","Error","probeReachable","timeoutMs","signal","AbortSignal","timeout"],"mappings":"AAAA,MAAMA,eAAe;AAErB,SAASC,MAAMC,EAAU;IACvB,OAAO,IAAIC,QAAQ,CAACC,UAAYC,WAAWD,SAASF;AACtD;AAEA,2FAA2F;AAC3F,+FAA+F;AAC/F,OAAO,eAAeI,eAAeC,GAAW,EAAEC,IAAiB,EAAEC,cAAc,GAAG;IACpF,IAAK,IAAIC,UAAU,GAAGA,UAAUV,cAAcU,UAAW;QACvD,IAAI;YACF,MAAMC,MAAM,MAAMC,MAAML,KAAKC;YAC7B,IAAIG,IAAIE,EAAE,IAAKF,IAAIG,MAAM,KAAK,OAAOH,IAAIG,MAAM,GAAG,OAAQJ,YAAYV,eAAe,GAAG,OAAOW;YAC/F,MAAMI,aAAaC,OAAOL,IAAIM,OAAO,CAACC,GAAG,CAAC;YAC1C,iFAAiF;YACjF,MAAMjB,MAAMc,aAAa,IAAII,KAAKC,GAAG,CAACL,aAAa,MAAM,SAAUN,cAAc,KAAKC;QACxF,EAAE,OAAOW,KAAK;YACZ,IAAIX,YAAYV,eAAe,GAAG,MAAMqB;YACxC,MAAMpB,MAAMQ,cAAc,KAAKC;QACjC;IACF;IACA,MAAM,IAAIY,MAAM;AAClB;AAEA,8FAA8F;AAC9F,2FAA2F;AAC3F,OAAO,eAAeC,eAAehB,GAAW,EAAEiB,YAAY,IAAI;IAChE,IAAI;QACF,MAAMZ,MAAML,KAAK;YAAEkB,QAAQC,YAAYC,OAAO,CAACH;QAAW;QAC1D,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/http.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\n\nconst MAX_ATTEMPTS = 3;\nconst MAX_RETRY_AFTER_MS = 10_000;\nconst DEFAULT_BASE_DELAY_MS = 200;\n\n// Initial policy bounds small API/metadata responses separately from model-file downloads.\nexport const HTTP_ATTEMPT_TIMEOUT_MS = 30_000;\nexport const MODEL_FILE_ATTEMPT_TIMEOUT_MS = 5 * 60_000;\n\nexport type FetchRequestInit = Omit<RequestInit, 'signal'> & { signal?: never };\n\nexport interface FetchRetryOptions {\n signal?: AbortSignal;\n attemptTimeoutMs?: number;\n baseDelayMs?: number;\n}\n\nexport interface BufferedResponse {\n readonly ok: boolean;\n readonly status: number;\n readonly headers: Headers;\n readonly body: ArrayBuffer;\n readonly url: string;\n}\n\ntype ResponsePhase = 'response headers' | 'response body';\n\nfunction safeUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.username = '';\n parsed.password = '';\n parsed.search = '';\n parsed.hash = '';\n return parsed.href;\n } catch {\n return 'the configured embedding endpoint';\n }\n}\n\nfunction attemptController(\n callerSignal: AbortSignal | undefined,\n timeoutMs: number\n): {\n controller: AbortController;\n timeoutReason: object;\n cleanup: () => void;\n} {\n callerSignal?.throwIfAborted();\n const controller = new AbortController();\n const timeoutReason = {};\n const relayAbort = () => controller.abort(callerSignal?.reason);\n callerSignal?.addEventListener('abort', relayAbort, { once: true });\n if (callerSignal?.aborted) relayAbort();\n const timer = setTimeout(() => controller.abort(timeoutReason), timeoutMs);\n return {\n controller,\n timeoutReason,\n cleanup: () => {\n clearTimeout(timer);\n callerSignal?.removeEventListener('abort', relayAbort);\n },\n };\n}\n\nfunction disposeResponse(response: Response, controller: AbortController): void {\n const cancellation = response.body && !response.bodyUsed ? response.body.cancel() : undefined;\n controller.abort();\n // The controller owns socket cleanup; observe stream cancellation without delaying the attempt.\n if (cancellation)\n void cancellation.then(\n () => undefined,\n () => undefined\n );\n}\n\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n signal?.throwIfAborted();\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n };\n const onAbort = () => {\n cleanup();\n reject(signal.reason);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n if (signal.aborted) onAbort();\n });\n}\n\nfunction failedRequest(url: string, phase: ResponsePhase, attempts: number, timeoutMs: number, timedOut: boolean): SenseError {\n const endpoint = safeUrl(url);\n if (timedOut) {\n return new SenseError('EMBED_MODEL', `${endpoint} timed out after ${timeoutMs}ms while waiting for ${phase} (attempt ${attempts}/${MAX_ATTEMPTS}); check the embedding service and network`);\n }\n return new SenseError('EMBED_MODEL', `${endpoint} failed while waiting for ${phase} after ${attempts} attempts; check the embedding service and network`);\n}\n\nfunction buffered(response: Response, body: ArrayBuffer, requestUrl: string): BufferedResponse {\n return {\n ok: response.ok,\n status: response.status,\n headers: response.headers,\n body,\n url: safeUrl(response.url || requestUrl),\n };\n}\n\n// Each attempt covers response headers and the complete successful body. Retry cleanup and\n// backoff honor one caller signal without transferring ownership of that signal.\nexport async function fetchWithRetry(url: string, init: FetchRequestInit, options: FetchRetryOptions = {}): Promise<BufferedResponse> {\n const timeoutMs = options.attemptTimeoutMs ?? HTTP_ATTEMPT_TIMEOUT_MS;\n const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n options.signal?.throwIfAborted();\n const { controller, timeoutReason, cleanup } = attemptController(options.signal, timeoutMs);\n let response: Response | undefined;\n let phase: ResponsePhase = 'response headers';\n let retryDelayMs: number | undefined;\n try {\n response = await fetch(url, { ...init, signal: controller.signal });\n if (!response.ok) {\n const retryable = response.status === 429 || response.status >= 500;\n if (retryable && attempt < MAX_ATTEMPTS - 1) {\n const retryAfter = Number(response.headers.get('retry-after'));\n retryDelayMs = retryAfter > 0 ? Math.min(retryAfter * 1000, MAX_RETRY_AFTER_MS) : baseDelayMs * 2 ** attempt;\n disposeResponse(response, controller);\n } else {\n disposeResponse(response, controller);\n options.signal?.throwIfAborted();\n return buffered(response, new ArrayBuffer(0), url);\n }\n } else {\n phase = 'response body';\n const body = await response.arrayBuffer();\n options.signal?.throwIfAborted();\n return buffered(response, body, url);\n }\n } catch {\n try {\n options.signal?.throwIfAborted();\n } finally {\n if (response) disposeResponse(response, controller);\n }\n const timedOut = controller.signal.reason === timeoutReason;\n if (attempt === MAX_ATTEMPTS - 1) throw failedRequest(url, phase, attempt + 1, timeoutMs, timedOut);\n retryDelayMs = baseDelayMs * 2 ** attempt;\n } finally {\n cleanup();\n }\n await sleep(retryDelayMs ?? baseDelayMs * 2 ** attempt, options.signal);\n }\n throw new Error('unreachable');\n}\n\n// Any HTTP response means the endpoint is up. Abort it immediately because status does not\n// consume its body, and bound the headers wait for an air-gapped machine.\nexport async function probeReachable(url: string, timeoutMs = 1500): Promise<boolean> {\n const { controller, cleanup } = attemptController(undefined, timeoutMs);\n try {\n const response = await fetch(url, { signal: controller.signal });\n disposeResponse(response, controller);\n return true;\n } catch {\n return false;\n } finally {\n cleanup();\n }\n}\n"],"names":["SenseError","MAX_ATTEMPTS","MAX_RETRY_AFTER_MS","DEFAULT_BASE_DELAY_MS","HTTP_ATTEMPT_TIMEOUT_MS","MODEL_FILE_ATTEMPT_TIMEOUT_MS","safeUrl","url","parsed","URL","username","password","search","hash","href","attemptController","callerSignal","timeoutMs","throwIfAborted","controller","AbortController","timeoutReason","relayAbort","abort","reason","addEventListener","once","aborted","timer","setTimeout","cleanup","clearTimeout","removeEventListener","disposeResponse","response","cancellation","body","bodyUsed","cancel","undefined","then","sleep","ms","signal","Promise","resolve","reject","onAbort","failedRequest","phase","attempts","timedOut","endpoint","buffered","requestUrl","ok","status","headers","fetchWithRetry","init","options","attemptTimeoutMs","baseDelayMs","attempt","retryDelayMs","fetch","retryable","retryAfter","Number","get","Math","min","ArrayBuffer","arrayBuffer","Error","probeReachable"],"mappings":"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"}
|
package/dist/esm/embed/openai.js
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
import { SenseError } from '../errors.js';
|
|
2
|
-
import {
|
|
2
|
+
import { effectiveEmbedEndpoint } from './endpoint.js';
|
|
3
|
+
import { fetchWithRetry, HTTP_ATTEMPT_TIMEOUT_MS } from './http.js';
|
|
3
4
|
const BATCH_CAP = 64;
|
|
5
|
+
function isEmbedding(value) {
|
|
6
|
+
if (value === null || typeof value !== 'object' || !('embedding' in value) || !Array.isArray(value.embedding) || !value.embedding.every((part)=>typeof part === 'number')) return false;
|
|
7
|
+
return !('index' in value) || value.index === undefined || typeof value.index === 'number';
|
|
8
|
+
}
|
|
9
|
+
function parseBody(body, endpoint) {
|
|
10
|
+
let decoded;
|
|
11
|
+
try {
|
|
12
|
+
decoded = JSON.parse(new TextDecoder().decode(body));
|
|
13
|
+
} catch {
|
|
14
|
+
throw new SenseError('EMBED_MODEL', `${endpoint} returned invalid JSON`);
|
|
15
|
+
}
|
|
16
|
+
if (decoded === null || typeof decoded !== 'object' || !('data' in decoded) || !Array.isArray(decoded.data) || !decoded.data.every(isEmbedding)) {
|
|
17
|
+
throw new SenseError('EMBED_MODEL', `${endpoint} returned an invalid embedding response`);
|
|
18
|
+
}
|
|
19
|
+
return decoded.data;
|
|
20
|
+
}
|
|
4
21
|
// One POST against any OpenAI-compatible /embeddings endpoint: Ollama, LM Studio, Cloudflare
|
|
5
22
|
// Workers AI, Jina, Voyage, and Gemini's compat base URL all serve this shape.
|
|
6
23
|
export async function openaiProvider(model, url, keyEnv) {
|
|
7
|
-
|
|
8
|
-
|
|
24
|
+
const base = effectiveEmbedEndpoint('openai', url);
|
|
25
|
+
if (!base) throw new SenseError('EMBED_MODEL', 'embed.provider "openai" requires a url');
|
|
9
26
|
const headers = {
|
|
10
27
|
'content-type': 'application/json'
|
|
11
28
|
};
|
|
12
29
|
const key = keyEnv ? process.env[keyEnv] : undefined;
|
|
13
30
|
if (key) headers.authorization = `Bearer ${key}`;
|
|
14
|
-
async function post(texts) {
|
|
31
|
+
async function post(texts, options) {
|
|
32
|
+
var _options_signal;
|
|
15
33
|
const res = await fetchWithRetry(`${base}/embeddings`, {
|
|
16
34
|
method: 'POST',
|
|
17
35
|
headers,
|
|
@@ -19,17 +37,21 @@ export async function openaiProvider(model, url, keyEnv) {
|
|
|
19
37
|
model,
|
|
20
38
|
input: texts
|
|
21
39
|
})
|
|
40
|
+
}, {
|
|
41
|
+
signal: options === null || options === void 0 ? void 0 : options.signal,
|
|
42
|
+
attemptTimeoutMs: HTTP_ATTEMPT_TIMEOUT_MS
|
|
22
43
|
});
|
|
23
|
-
|
|
24
|
-
|
|
44
|
+
options === null || options === void 0 ? void 0 : (_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
|
|
45
|
+
if (!res.ok) throw new SenseError('EMBED_MODEL', `${res.url} -> HTTP ${res.status}`);
|
|
46
|
+
const body = parseBody(res.body, res.url);
|
|
25
47
|
// The schema carries `index` because response order is not promised, and the caller maps
|
|
26
48
|
// vectors back to chunks positionally: sort by it whenever every element has one.
|
|
27
|
-
const data = body.
|
|
28
|
-
...body
|
|
29
|
-
].sort((a, b)=>a.index - b.index) : body
|
|
49
|
+
const data = body.every((d)=>typeof d.index === 'number') ? [
|
|
50
|
+
...body
|
|
51
|
+
].sort((a, b)=>a.index - b.index) : body;
|
|
30
52
|
// A server that drops an input (empty or over-length strings are the usual cause) would
|
|
31
53
|
// otherwise shift every later vector onto the wrong chunk, silently.
|
|
32
|
-
if (data.length !== texts.length) throw new SenseError('EMBED_MODEL', `${
|
|
54
|
+
if (data.length !== texts.length) throw new SenseError('EMBED_MODEL', `${res.url} returned ${data.length} embeddings for ${texts.length} inputs`);
|
|
33
55
|
return data.map((d)=>Float32Array.from(d.embedding));
|
|
34
56
|
}
|
|
35
57
|
const dims = (await post([
|
|
@@ -42,8 +64,8 @@ export async function openaiProvider(model, url, keyEnv) {
|
|
|
42
64
|
dims,
|
|
43
65
|
batchCap: BATCH_CAP,
|
|
44
66
|
embedDocuments: post,
|
|
45
|
-
embedQuery: async (text)=>(await post([
|
|
67
|
+
embedQuery: async (text, options)=>(await post([
|
|
46
68
|
text
|
|
47
|
-
]))[0]
|
|
69
|
+
], options))[0]
|
|
48
70
|
};
|
|
49
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/openai.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { fetchWithRetry } from './http.ts';\nimport type { EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 64;\n\n// One POST against any OpenAI-compatible /embeddings endpoint: Ollama, LM Studio, Cloudflare\n// Workers AI, Jina, Voyage, and Gemini's compat base URL all serve this shape.\nexport async function openaiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n if (!
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/openai.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { effectiveEmbedEndpoint } from './endpoint.ts';\nimport { fetchWithRetry, HTTP_ATTEMPT_TIMEOUT_MS } from './http.ts';\nimport type { EmbedCallOptions, EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 64;\n\ninterface OpenaiEmbedding {\n embedding: number[];\n index?: number;\n}\n\nfunction isEmbedding(value: unknown): value is OpenaiEmbedding {\n if (value === null || typeof value !== 'object' || !('embedding' in value) || !Array.isArray(value.embedding) || !value.embedding.every((part) => typeof part === 'number')) return false;\n return !('index' in value) || value.index === undefined || typeof value.index === 'number';\n}\n\nfunction parseBody(body: ArrayBuffer, endpoint: string): OpenaiEmbedding[] {\n let decoded: unknown;\n try {\n decoded = JSON.parse(new TextDecoder().decode(body));\n } catch {\n throw new SenseError('EMBED_MODEL', `${endpoint} returned invalid JSON`);\n }\n if (decoded === null || typeof decoded !== 'object' || !('data' in decoded) || !Array.isArray(decoded.data) || !decoded.data.every(isEmbedding)) {\n throw new SenseError('EMBED_MODEL', `${endpoint} returned an invalid embedding response`);\n }\n return decoded.data;\n}\n\n// One POST against any OpenAI-compatible /embeddings endpoint: Ollama, LM Studio, Cloudflare\n// Workers AI, Jina, Voyage, and Gemini's compat base URL all serve this shape.\nexport async function openaiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n const base = effectiveEmbedEndpoint('openai', url);\n if (!base) throw new SenseError('EMBED_MODEL', 'embed.provider \"openai\" requires a url');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[], options?: EmbedCallOptions): Promise<Float32Array[]> {\n const res = await fetchWithRetry(`${base}/embeddings`, { method: 'POST', headers, body: JSON.stringify({ model, input: texts }) }, { signal: options?.signal, attemptTimeoutMs: HTTP_ATTEMPT_TIMEOUT_MS });\n options?.signal?.throwIfAborted();\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${res.url} -> HTTP ${res.status}`);\n const body = parseBody(res.body, res.url);\n // The schema carries `index` because response order is not promised, and the caller maps\n // vectors back to chunks positionally: sort by it whenever every element has one.\n const data = body.every((d): d is OpenaiEmbedding & { index: number } => typeof d.index === 'number') ? [...body].sort((a, b) => a.index - b.index) : body;\n // A server that drops an input (empty or over-length strings are the usual cause) would\n // otherwise shift every later vector onto the wrong chunk, silently.\n if (data.length !== texts.length) throw new SenseError('EMBED_MODEL', `${res.url} returned ${data.length} embeddings for ${texts.length} inputs`);\n return data.map((d) => Float32Array.from(d.embedding));\n }\n\n const dims = (await post(['dimension probe']))[0].length;\n // embedQuery and embedDocuments are symmetric here; per-model prefixes like nomic's\n // search_document/search_query are not applied.\n return { id: `openai:${base}:${model}`, dims, batchCap: BATCH_CAP, embedDocuments: post, embedQuery: async (text, options) => (await post([text], options))[0] };\n}\n"],"names":["SenseError","effectiveEmbedEndpoint","fetchWithRetry","HTTP_ATTEMPT_TIMEOUT_MS","BATCH_CAP","isEmbedding","value","Array","isArray","embedding","every","part","index","undefined","parseBody","body","endpoint","decoded","JSON","parse","TextDecoder","decode","data","openaiProvider","model","url","keyEnv","base","headers","key","process","env","authorization","post","texts","options","res","method","stringify","input","signal","attemptTimeoutMs","throwIfAborted","ok","status","d","sort","a","b","length","map","Float32Array","from","dims","id","batchCap","embedDocuments","embedQuery","text"],"mappings":"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"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Config } from '../config/index.js';
|
|
2
2
|
import type { Store, VectorCandidate, VectorSimilar } from '../store/types.js';
|
|
3
|
+
import { type EmbedCallOptions } from './types.js';
|
|
3
4
|
export declare function toStore(full: Float32Array, dims: number, int8: boolean): {
|
|
4
5
|
v: Float32Array;
|
|
5
6
|
scale: number;
|
|
6
7
|
};
|
|
7
|
-
export declare function embedPending(store: Store, cfg: Config, allowed?: ReadonlySet<string
|
|
8
|
+
export declare function embedPending(store: Store, cfg: Config, allowed?: ReadonlySet<string>, expectedSignature?: string | null, options?: EmbedCallOptions): Promise<void>;
|
|
8
9
|
export declare function ensureDocumentEmbeddings(store: Store, _cfg: Config, allowed: ReadonlySet<string>): Promise<void>;
|
|
9
|
-
export
|
|
10
|
+
export interface PreparedSemanticQuery {
|
|
11
|
+
readonly vector: Float32Array;
|
|
12
|
+
readonly storeDims: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function prepareSemanticQuery(cfg: Config, terms: string, options?: EmbedCallOptions): Promise<PreparedSemanticQuery>;
|
|
15
|
+
export declare function semanticCandidates(store: Store, prepared: PreparedSemanticQuery, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;
|
|
10
16
|
export declare function hasEmbedding(store: Store, path: string): Promise<boolean>;
|
|
11
17
|
export declare function similarNotes(store: Store, _cfg: Config, path: string, opts: {
|
|
12
18
|
exclude: Set<string>;
|
package/dist/esm/embed/query.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { featureSignature } from '../config/index.js';
|
|
1
2
|
import { SenseError } from '../errors.js';
|
|
2
3
|
import { embed } from '../features/embed.js';
|
|
4
|
+
import { FEATURES } from '../features/index.js';
|
|
3
5
|
import { progress } from '../output/progress.js';
|
|
4
6
|
import { parseSource } from '../scan/index.js';
|
|
5
|
-
import {
|
|
7
|
+
import { FEATURE_SIGNATURE_META_KEY } from '../store/readiness.js';
|
|
8
|
+
import { getMeta } from '../store/shared.js';
|
|
9
|
+
import { embeddingSignatureCompatible } from '../store/signature.js';
|
|
10
|
+
import { identifyChunkText, takeChunkText } from './handoff.js';
|
|
6
11
|
import { checkLanguageFit } from './langfit.js';
|
|
7
12
|
import { getProvider } from './registry.js';
|
|
8
13
|
import { STORE_DIMS } from './types.js';
|
|
@@ -28,38 +33,64 @@ export function toStore(full, dims, int8) {
|
|
|
28
33
|
// Embed rows whose vector is NULL. Reconcile hands its chunk text over in memory when it ran in
|
|
29
34
|
// this process (handoff.ts); a row left pending by an earlier command re-derives from the exact
|
|
30
35
|
// source string persisted with that indexed generation.
|
|
31
|
-
|
|
36
|
+
function staleEmbeddingWork(reason) {
|
|
37
|
+
return new SenseError('INDEX_NOT_READY', `${reason}; run \`sense build\` (or library build(config)) again to prepare the current index generation`);
|
|
38
|
+
}
|
|
39
|
+
// expectedSignature is captured by open() before provider work. Direct internal callers omit it
|
|
40
|
+
// and capture the durable value here, before selecting chunks or constructing a provider.
|
|
41
|
+
export async function embedPending(store, cfg, allowed, expectedSignature, options = {}) {
|
|
32
42
|
var _takeChunkText;
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
var _options_signal, _options_signal1, _options_signal2, _options_signal3, // Over the exact texts about to be embedded, before any of them are: a mismatch fails
|
|
44
|
+
// this run loudly instead of quietly storing vectors from the wrong model.
|
|
45
|
+
_options_signal4, _options_signal5;
|
|
46
|
+
(_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
|
|
47
|
+
const expectedDurableSignature = expectedSignature === undefined ? await getMeta(store, FEATURE_SIGNATURE_META_KEY) : expectedSignature;
|
|
48
|
+
(_options_signal1 = options.signal) === null || _options_signal1 === void 0 ? void 0 : _options_signal1.throwIfAborted();
|
|
49
|
+
if (!embeddingSignatureCompatible(expectedDurableSignature, featureSignature(cfg, FEATURES))) {
|
|
50
|
+
throw staleEmbeddingWork('the index configuration changed before vectors could be prepared');
|
|
51
|
+
}
|
|
52
|
+
const pendingStmt = await store.prepare('SELECT "path", chunk, content_identity FROM embeddings WHERE vector IS NULL ORDER BY "path", chunk');
|
|
53
|
+
const dirty = (await pendingStmt.all()).filter((row)=>{
|
|
35
54
|
var _ref;
|
|
36
55
|
return (_ref = allowed === null || allowed === void 0 ? void 0 : allowed.has(row.path)) !== null && _ref !== void 0 ? _ref : true;
|
|
37
56
|
});
|
|
57
|
+
(_options_signal2 = options.signal) === null || _options_signal2 === void 0 ? void 0 : _options_signal2.throwIfAborted();
|
|
38
58
|
if (dirty.length === 0) return;
|
|
59
|
+
const provider = await getProvider(cfg, options);
|
|
60
|
+
(_options_signal3 = options.signal) === null || _options_signal3 === void 0 ? void 0 : _options_signal3.throwIfAborted();
|
|
61
|
+
const submittedSignature = featureSignature(cfg, FEATURES);
|
|
62
|
+
if (!embeddingSignatureCompatible(expectedDurableSignature, submittedSignature)) {
|
|
63
|
+
throw staleEmbeddingWork('the index configuration changed while the embedding provider was being prepared');
|
|
64
|
+
}
|
|
39
65
|
const storeDims = Math.min(STORE_DIMS, provider.dims);
|
|
40
66
|
const byPath = new Map();
|
|
41
67
|
for (const row of dirty){
|
|
42
68
|
var _byPath_get;
|
|
43
69
|
const list = (_byPath_get = byPath.get(row.path)) !== null && _byPath_get !== void 0 ? _byPath_get : [];
|
|
44
|
-
list.push(row
|
|
70
|
+
list.push(row);
|
|
45
71
|
byPath.set(row.path, list);
|
|
46
72
|
}
|
|
47
73
|
const textByPath = (_takeChunkText = takeChunkText(store)) !== null && _takeChunkText !== void 0 ? _takeChunkText : new Map();
|
|
48
74
|
const sourceStmt = await store.prepare('SELECT text FROM indexed_sources WHERE "path" = ?');
|
|
49
75
|
const jobs = [];
|
|
50
|
-
for (const [path,
|
|
76
|
+
for (const [path, pending] of byPath){
|
|
77
|
+
var _options_signal6;
|
|
51
78
|
const texts = textByPath.get(path);
|
|
52
|
-
if (texts &&
|
|
53
|
-
|
|
79
|
+
if (texts && pending.every((row)=>{
|
|
80
|
+
var _texts_row_chunk;
|
|
81
|
+
return ((_texts_row_chunk = texts[row.chunk]) === null || _texts_row_chunk === void 0 ? void 0 : _texts_row_chunk.identity) === row.content_identity;
|
|
82
|
+
})) {
|
|
83
|
+
for (const row of pending)jobs.push({
|
|
54
84
|
path,
|
|
55
|
-
chunk:
|
|
56
|
-
|
|
85
|
+
chunk: row.chunk,
|
|
86
|
+
...texts[row.chunk]
|
|
57
87
|
});
|
|
58
88
|
continue;
|
|
59
89
|
}
|
|
60
90
|
// A prior process may have reconciled the row, so its in-memory handoff is gone. Re-derive
|
|
61
91
|
// from that indexed generation's stored source, never from a newer live file.
|
|
62
92
|
const source = await sourceStmt.get(path);
|
|
93
|
+
(_options_signal6 = options.signal) === null || _options_signal6 === void 0 ? void 0 : _options_signal6.throwIfAborted();
|
|
63
94
|
if (!source) throw new SenseError('INDEX_NOT_READY', `indexed source text is missing for ${path}; run \`sense build --force\` (or library build(config, { force: true })) before preparing vectors`);
|
|
64
95
|
const chunks = parseSource({
|
|
65
96
|
relPath: path,
|
|
@@ -72,26 +103,33 @@ export async function embedPending(store, cfg, allowed) {
|
|
|
72
103
|
}, source.text, [
|
|
73
104
|
embed
|
|
74
105
|
], cfg).doc.extracted.embed;
|
|
75
|
-
for (const
|
|
76
|
-
const chunk = chunks[
|
|
106
|
+
for (const row of pending){
|
|
107
|
+
const chunk = chunks[row.chunk];
|
|
77
108
|
if (!chunk) throw new SenseError('INDEX_NOT_READY', `indexed vector metadata for ${path} does not match its indexed source; run \`sense build --force\` (or library build(config, { force: true }))`);
|
|
109
|
+
const identified = identifyChunkText(chunk.text);
|
|
110
|
+
if (identified.identity !== row.content_identity) throw staleEmbeddingWork(`indexed chunk ${path}#${row.chunk} changed before vectors could be prepared`);
|
|
78
111
|
jobs.push({
|
|
79
112
|
path,
|
|
80
|
-
chunk:
|
|
81
|
-
|
|
113
|
+
chunk: row.chunk,
|
|
114
|
+
...identified
|
|
82
115
|
});
|
|
83
116
|
}
|
|
84
117
|
}
|
|
85
|
-
|
|
86
|
-
// this run loudly instead of quietly storing vectors from the wrong model.
|
|
118
|
+
(_options_signal4 = options.signal) === null || _options_signal4 === void 0 ? void 0 : _options_signal4.throwIfAborted();
|
|
87
119
|
await checkLanguageFit(store, provider, jobs.map((j)=>j.text));
|
|
120
|
+
(_options_signal5 = options.signal) === null || _options_signal5 === void 0 ? void 0 : _options_signal5.throwIfAborted();
|
|
88
121
|
// Preparing a large configured scope can take long enough to look stalled, so report each
|
|
89
122
|
// completed provider batch.
|
|
90
123
|
const report = progress('embedding chunks', jobs.length);
|
|
91
124
|
for(let i = 0; i < jobs.length; i += provider.batchCap){
|
|
125
|
+
var _options_signal7, _options_signal8, _options_signal9;
|
|
92
126
|
const batch = jobs.slice(i, i + provider.batchCap);
|
|
93
|
-
|
|
94
|
-
|
|
127
|
+
(_options_signal7 = options.signal) === null || _options_signal7 === void 0 ? void 0 : _options_signal7.throwIfAborted();
|
|
128
|
+
if (featureSignature(cfg, FEATURES) !== submittedSignature) throw staleEmbeddingWork('the embedding configuration changed before a provider batch was submitted');
|
|
129
|
+
const vectors = await provider.embedDocuments(batch.map((j)=>j.text), options);
|
|
130
|
+
(_options_signal8 = options.signal) === null || _options_signal8 === void 0 ? void 0 : _options_signal8.throwIfAborted();
|
|
131
|
+
if (featureSignature(cfg, FEATURES) !== submittedSignature) throw staleEmbeddingWork('the embedding configuration changed while a provider batch was running');
|
|
132
|
+
const writes = batch.map((job, j)=>{
|
|
95
133
|
const { v, scale } = toStore(vectors[j], storeDims, true);
|
|
96
134
|
const q = new Int8Array(storeDims);
|
|
97
135
|
for(let d = 0; d < storeDims; d++)q[d] = Math.round(v[d] / scale);
|
|
@@ -101,7 +139,28 @@ export async function embedPending(store, cfg, allowed) {
|
|
|
101
139
|
scale,
|
|
102
140
|
vector: Buffer.from(q.buffer)
|
|
103
141
|
};
|
|
104
|
-
})
|
|
142
|
+
});
|
|
143
|
+
(_options_signal9 = options.signal) === null || _options_signal9 === void 0 ? void 0 : _options_signal9.throwIfAborted();
|
|
144
|
+
await store.transaction(async ()=>{
|
|
145
|
+
var // Write first so SQLite owns the write lock before the fence reads. Any mismatch throws
|
|
146
|
+
// inside this transaction and rolls every row in the provider batch back together.
|
|
147
|
+
_options_signal, _options_signal1, _options_signal2;
|
|
148
|
+
(_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
|
|
149
|
+
await store.vectors.writeVectors(writes);
|
|
150
|
+
(_options_signal1 = options.signal) === null || _options_signal1 === void 0 ? void 0 : _options_signal1.throwIfAborted();
|
|
151
|
+
if (await getMeta(store, FEATURE_SIGNATURE_META_KEY) !== expectedDurableSignature) throw staleEmbeddingWork('the durable embedding configuration changed while a provider batch was running');
|
|
152
|
+
const currentStmt = await store.prepare(`SELECT "path", chunk, content_identity FROM embeddings WHERE ${batch.map(()=>'("path" = ? AND chunk = ?)').join(' OR ')}`);
|
|
153
|
+
const current = await currentStmt.all(...batch.flatMap((job)=>[
|
|
154
|
+
job.path,
|
|
155
|
+
job.chunk
|
|
156
|
+
]));
|
|
157
|
+
const identities = new Map(current.map((row)=>[
|
|
158
|
+
`${row.path}\0${Number(row.chunk)}`,
|
|
159
|
+
row.content_identity
|
|
160
|
+
]));
|
|
161
|
+
if (batch.some((job)=>identities.get(`${job.path}\0${job.chunk}`) !== job.identity)) throw staleEmbeddingWork('indexed chunks changed while a provider batch was running');
|
|
162
|
+
(_options_signal2 = options.signal) === null || _options_signal2 === void 0 ? void 0 : _options_signal2.throwIfAborted();
|
|
163
|
+
});
|
|
105
164
|
report.tick(Math.min(i + provider.batchCap, jobs.length));
|
|
106
165
|
}
|
|
107
166
|
report.finish();
|
|
@@ -112,22 +171,35 @@ export async function ensureDocumentEmbeddings(store, _cfg, allowed) {
|
|
|
112
171
|
throw new SenseError('INDEX_NOT_READY', `${pending.length} indexed chunk(s) in the requested scope still need vectors; run \`sense build\` (or library build(config)) before querying with build disabled`);
|
|
113
172
|
}
|
|
114
173
|
}
|
|
115
|
-
//
|
|
116
|
-
// lexical syntax
|
|
117
|
-
export async function
|
|
174
|
+
// Provider/model work is independent of the index snapshot. FTS5 operators are stripped as
|
|
175
|
+
// lexical syntax before the query is embedded, matching the prior semantic query behavior.
|
|
176
|
+
export async function prepareSemanticQuery(cfg, terms, options = {}) {
|
|
118
177
|
var _cfg_rootDir, _terms_match;
|
|
178
|
+
var _options_signal, _options_signal1, _options_signal2;
|
|
119
179
|
const rootDir = (_cfg_rootDir = cfg.rootDir) !== null && _cfg_rootDir !== void 0 ? _cfg_rootDir : cfg.baseDir;
|
|
120
180
|
if (!rootDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a resolved config (use loadConfig/open)');
|
|
121
|
-
const provider = await getProvider(cfg);
|
|
122
|
-
const storeDims = Math.min(STORE_DIMS, provider.dims);
|
|
123
181
|
const text = ((_terms_match = terms.match(/[\p{L}\p{N}]+/gu)) !== null && _terms_match !== void 0 ? _terms_match : []).filter((t)=>![
|
|
124
182
|
'AND',
|
|
125
183
|
'OR',
|
|
126
184
|
'NOT',
|
|
127
185
|
'NEAR'
|
|
128
186
|
].includes(t)).join(' ');
|
|
129
|
-
|
|
130
|
-
|
|
187
|
+
(_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
|
|
188
|
+
const provider = await getProvider(cfg, options);
|
|
189
|
+
(_options_signal1 = options.signal) === null || _options_signal1 === void 0 ? void 0 : _options_signal1.throwIfAborted();
|
|
190
|
+
const storeDims = Math.min(STORE_DIMS, provider.dims);
|
|
191
|
+
const queryVector = await provider.embedQuery(text, options);
|
|
192
|
+
(_options_signal2 = options.signal) === null || _options_signal2 === void 0 ? void 0 : _options_signal2.throwIfAborted();
|
|
193
|
+
const { v: qv } = toStore(queryVector, storeDims, false);
|
|
194
|
+
return {
|
|
195
|
+
vector: qv,
|
|
196
|
+
storeDims
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Best chunk per file by cosine, its line range riding along. The caller owns the read snapshot
|
|
200
|
+
// and the prepared vector, so this performs no provider work and only delegates to the store.
|
|
201
|
+
export function semanticCandidates(store, prepared, fetch, allowed) {
|
|
202
|
+
return store.vectors.candidates(prepared.vector, prepared.storeDims, fetch, allowed);
|
|
131
203
|
}
|
|
132
204
|
// Whether a note has any chunk with a vector. Distinguishes "nothing is near this note" from
|
|
133
205
|
// "this note has no text", which look the same in an empty result.
|