@hraness/sponge 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/src/acquisition/lock.d.ts +1 -1
- package/dist/src/acquisition/lock.js +1 -1
- package/dist/src/acquisition/pins.js +3 -3
- package/dist/src/acquisition/runtime.d.ts +1 -0
- package/dist/src/acquisition/runtime.js +4 -2
- package/dist/src/cli/main.js +1 -1
- package/dist/src/indexing/index.d.ts +1 -1
- package/dist/src/indexing/index.js +18 -10
- package/dist/src/inference/embedding/index.d.ts +3 -0
- package/dist/src/inference/embedding/index.js +4 -2
- package/native/runtime-manifest.json +4 -4
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { captureLockfile } from './lock.js';
|
|
2
2
|
/** Immutable released media/PDF capability pins; installed qualification is recorded separately. */
|
|
3
3
|
export const defaultCapturePins = {
|
|
4
|
-
id: 'capture-ghostget-0.18.
|
|
5
|
-
ghostget: { version: '0.18.
|
|
4
|
+
id: 'capture-ghostget-0.18.99-bun-1.3.14-darwin-arm64',
|
|
5
|
+
ghostget: { version: '0.18.99', url: 'https://github.com/hraness/ghostget/releases/download/v0.18.99/hraness-ghostget-0.18.99.tgz', bytes: 12470970, sha256: '91d226b2d69884f7119f11ae9316f37134c3da7e127e91ae210c982eced92530' },
|
|
6
6
|
wordcell: { version: '0.27.0', url: 'https://github.com/hraness/wordcell/releases/download/v0.27.0/hraness-wordcell-0.27.0.tgz', bytes: 1465897, sha256: 'df47868147cda46d741895d3cd1ddcbec14a563b341aa09052545be7696c9262' },
|
|
7
7
|
bun: { version: '1.3.14', url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-darwin-aarch64.zip', bytes: 23586433, sha256: 'd8b96221828ad6f97ac7ac0ab7e95872341af763001e8803e8267652c2652620', executableSha256: 'e0c90ec15d33363e6b70713d56bc3b2c7585c17f40a0fe0f8fd9305901d4e233', archiveExecutable: 'bun-darwin-aarch64/bun' },
|
|
8
|
-
lockfile: captureLockfile, lockSha256: "
|
|
8
|
+
lockfile: captureLockfile, lockSha256: "1dec6b6e96f6e2dc7ddecafb087ca5599b438a7d282e7b7d88aca5ebad1e9e6c",
|
|
9
9
|
};
|
|
@@ -32,6 +32,7 @@ export interface SetupPlan {
|
|
|
32
32
|
installationBudgetBytes: number;
|
|
33
33
|
stagingBudgetBytes: number;
|
|
34
34
|
installationBudgetKind: 'monitored';
|
|
35
|
+
installationDeadlineMs: number;
|
|
35
36
|
dependencyDownloads: 'frozen-lock';
|
|
36
37
|
requiredFreeBytes: number;
|
|
37
38
|
capabilities: string[];
|
|
@@ -14,6 +14,8 @@ export class CaptureSetupError extends Error {
|
|
|
14
14
|
// Retained inventory excludes the isolated Bun cache and temporary Bun zip.
|
|
15
15
|
const budget = 1024 ** 3;
|
|
16
16
|
const stagingBudget = 2 * budget;
|
|
17
|
+
// The frozen dependency install measured 460 s on a slow link; 300 s aborted valid setups.
|
|
18
|
+
const installDeadlineMs = 30 * 60 * 1000;
|
|
17
19
|
function checkPins(pins) {
|
|
18
20
|
if (!/^[a-z0-9][a-z0-9.-]{0,99}$/.test(pins.id) || hash(pins.lockfile) !== pins.lockSha256)
|
|
19
21
|
throw new CaptureSetupError('invalid-input');
|
|
@@ -34,7 +36,7 @@ function checkPins(pins) {
|
|
|
34
36
|
export function captureSetupPlan(pins) {
|
|
35
37
|
checkPins(pins);
|
|
36
38
|
const artifacts = [pins.bun, pins.ghostget, pins.wordcell], downloadBytes = artifacts.reduce((n, a) => n + a.bytes, 0);
|
|
37
|
-
return { artifacts, downloadBytes, installationBudgetBytes: budget, stagingBudgetBytes: stagingBudget, installationBudgetKind: 'monitored', dependencyDownloads: 'frozen-lock', requiredFreeBytes: 2 * 1024 ** 3 + budget + stagingBudget + downloadBytes, capabilities: ['article-http', 'selected-ghostget-account', 'selected-attached-browser', 'verified-offline-images'] };
|
|
39
|
+
return { artifacts, downloadBytes, installationBudgetBytes: budget, stagingBudgetBytes: stagingBudget, installationBudgetKind: 'monitored', installationDeadlineMs: installDeadlineMs, dependencyDownloads: 'frozen-lock', requiredFreeBytes: 2 * 1024 ** 3 + budget + stagingBudget + downloadBytes, capabilities: ['article-http', 'selected-ghostget-account', 'selected-attached-browser', 'verified-offline-images'] };
|
|
38
40
|
}
|
|
39
41
|
async function download(pin, path, signal) {
|
|
40
42
|
const boundedSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(120000)]) : AbortSignal.timeout(120000);
|
|
@@ -158,7 +160,7 @@ async function provisionRuntime(options) {
|
|
|
158
160
|
await download(pins.wordcell, join(stage, `hraness-wordcell-${pins.wordcell.version}.tgz`), controller.signal);
|
|
159
161
|
await writeFile(join(stage, 'package.json'), JSON.stringify({ private: true, dependencies: { '@hraness/ghostget': `./hraness-ghostget-${pins.ghostget.version}.tgz` } }), { mode: 0o600 });
|
|
160
162
|
await writeFile(join(stage, 'bun.lock'), pins.lockfile, { mode: 0o600 });
|
|
161
|
-
const install = await runOwned(bun, ['install', '--frozen-lockfile', '--ignore-scripts'], { cwd: stage, env: isolatedEnvironment(home, home, home), signal: controller.signal, deadlineMs:
|
|
163
|
+
const install = await runOwned(bun, ['install', '--frozen-lockfile', '--ignore-scripts'], { cwd: stage, env: isolatedEnvironment(home, home, home), signal: controller.signal, deadlineMs: installDeadlineMs, maxOutputBytes: 65536 });
|
|
162
164
|
cleanup = install.cleanup;
|
|
163
165
|
if (install.code !== 0 || install.stopped || cleanup !== 'settled')
|
|
164
166
|
throw new CaptureSetupError(limitExceeded ? 'limit-exceeded' : install.stopped ?? 'unavailable');
|
package/dist/src/cli/main.js
CHANGED
|
@@ -124,7 +124,7 @@ export async function main(argv, version) {
|
|
|
124
124
|
}
|
|
125
125
|
if (args.command === 'setup') {
|
|
126
126
|
const plan = captureSetupPlan(defaultCapturePins);
|
|
127
|
-
process.stderr.write(`Capture setup: ${plan.downloadBytes} pinned artifact bytes, plus frozen dependency downloads. Temporary installation limit: ${plan.stagingBudgetBytes} bytes; retained runtime limit: ${plan.installationBudgetBytes} bytes. ${plan.requiredFreeBytes} free bytes required.\n`);
|
|
127
|
+
process.stderr.write(`Capture setup: ${plan.downloadBytes} pinned artifact bytes, plus frozen dependency downloads. Temporary installation limit: ${plan.stagingBudgetBytes} bytes; retained runtime limit: ${plan.installationBudgetBytes} bytes; dependency installation deadline: ${plan.installationDeadlineMs / 60000} minutes. ${plan.requiredFreeBytes} free bytes required.\n`);
|
|
128
128
|
await provisionCapture({ runtimeRoot: join(store.root, 'runtime', 'capture'), pins: defaultCapturePins, signal: controller.signal });
|
|
129
129
|
emit({ capture: 'ready' }, 'Capture ready');
|
|
130
130
|
return 0;
|
|
@@ -23,7 +23,7 @@ export declare class IndexError extends Error {
|
|
|
23
23
|
code: 'invalid-index' | 'stale-index' | 'incomplete-index' | 'cancelled-index';
|
|
24
24
|
constructor(code: 'invalid-index' | 'stale-index' | 'incomplete-index' | 'cancelled-index');
|
|
25
25
|
}
|
|
26
|
-
/** Unicode-safe chunks
|
|
26
|
+
/** Unicode-safe chunks fit the fixed document prompt within the embedding text budget, preferring whitespace breaks. */
|
|
27
27
|
export declare function chunkDocument(text: string): string[];
|
|
28
28
|
/** Identity must already be the verified embedder fingerprint. Existing identities are immutable. */
|
|
29
29
|
export declare function createGeneration(db: DatabaseSync, options: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { spaceFingerprint, encodeEmbeddingRequest } from '../inference/embedding/index.js';
|
|
2
|
+
import { spaceFingerprint, encodeEmbeddingRequest, embeddingTextBytes } from '../inference/embedding/index.js';
|
|
3
3
|
export class IndexError extends Error {
|
|
4
4
|
code;
|
|
5
5
|
constructor(code) {
|
|
@@ -21,17 +21,25 @@ catch (e) {
|
|
|
21
21
|
} }
|
|
22
22
|
function epoch(db) { return Number(db.prepare('SELECT revision_epoch FROM library_info WHERE id=1').get()?.revision_epoch); }
|
|
23
23
|
function revisions(db) { return db.prepare('SELECT id AS itemId,current_revision AS revisionId FROM items WHERE deleted_at IS NULL AND current_revision IS NOT NULL ORDER BY id').all(); }
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const chunkBytes = embeddingTextBytes - Buffer.byteLength('title: none | text: ');
|
|
25
|
+
/** Unicode-safe chunks fit the fixed document prompt within the embedding text budget, preferring whitespace breaks. */
|
|
26
|
+
export function chunkDocument(text) { const chunks = []; let part = '', size = 0, cut = -1, cutSize = 0; for (const char of text) {
|
|
26
27
|
const bytes = Buffer.byteLength(char);
|
|
27
|
-
if (size + bytes >
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
if (size + bytes > chunkBytes) {
|
|
29
|
+
const at = cut > 0 && cutSize * 2 >= chunkBytes ? cut : part.length;
|
|
30
|
+
const head = part.slice(0, at);
|
|
31
|
+
if (head.trim())
|
|
32
|
+
chunks.push(head);
|
|
33
|
+
part = part.slice(at);
|
|
34
|
+
size = Buffer.byteLength(part);
|
|
35
|
+
cut = -1;
|
|
32
36
|
}
|
|
33
37
|
part += char;
|
|
34
38
|
size += bytes;
|
|
39
|
+
if (/\s/u.test(char)) {
|
|
40
|
+
cut = part.length;
|
|
41
|
+
cutSize = size;
|
|
42
|
+
}
|
|
35
43
|
} if (part.trim())
|
|
36
44
|
chunks.push(part); return chunks; }
|
|
37
45
|
function snapshot(db) {
|
|
@@ -41,7 +49,7 @@ function snapshot(db) {
|
|
|
41
49
|
for (const doc of docs) {
|
|
42
50
|
const chunks = chunkDocument(String(doc.markdown));
|
|
43
51
|
for (let ordinal = 0; ordinal < chunks.length; ordinal++) {
|
|
44
|
-
const text = chunks[ordinal], derivationHash = hash(JSON.stringify(['sponge-chunk-
|
|
52
|
+
const text = chunks[ordinal], derivationHash = hash(JSON.stringify(['sponge-chunk-v2', String(doc.id), ordinal, text]));
|
|
45
53
|
inputs.push({ ...r, sourceId: JSON.stringify([r.revisionId, 'document', String(doc.id), ordinal]), kind: 'document', representationId: String(doc.id), ordinal, derivationHash });
|
|
46
54
|
}
|
|
47
55
|
}
|
|
@@ -73,7 +81,7 @@ export function pendingInputs(db, id, limit = 16) { if (!Number.isSafeInteger(li
|
|
|
73
81
|
fail('cancelled-index'); if (row.state !== 'building')
|
|
74
82
|
return []; const done = new Set(db.prepare('SELECT source_id FROM vectors WHERE generation_id=? AND input_hash IS NOT NULL').all(id).map(r => String(r.source_id))); return coverage.inputs.filter(i => !done.has(i.sourceId)).slice(0, limit).map(input => { if (input.kind === 'image')
|
|
75
83
|
return input; const doc = db.prepare('SELECT markdown FROM representations WHERE id=? AND revision_id=?').get(input.representationId, input.revisionId); if (!doc || typeof doc.markdown !== 'string')
|
|
76
|
-
return fail(); const text = chunkDocument(doc.markdown)[input.ordinal]; if (text === undefined || hash(JSON.stringify(['sponge-chunk-
|
|
84
|
+
return fail(); const text = chunkDocument(doc.markdown)[input.ordinal]; if (text === undefined || hash(JSON.stringify(['sponge-chunk-v2', input.representationId, input.ordinal, text])) !== input.derivationHash)
|
|
77
85
|
return fail(); return { ...input, text }; }); }
|
|
78
86
|
export function writeBatch(db, id, fingerprint, batch) {
|
|
79
87
|
if (!batch.length || batch.length > 256)
|
|
@@ -9,7 +9,10 @@ export declare const embeddingIdentity: {
|
|
|
9
9
|
readonly prompt: "search-result-query-title-none-v1";
|
|
10
10
|
readonly protocol: 1;
|
|
11
11
|
readonly backend: "cpu-2threads-f32";
|
|
12
|
+
readonly textBudget: "utf8-1016-v1";
|
|
12
13
|
};
|
|
14
|
+
/** The model's largest text signature is 1024 tokens; byte-fallback tokenization never yields more tokens than UTF-8 bytes. */
|
|
15
|
+
export declare const embeddingTextBytes = 1016;
|
|
13
16
|
export type EmbeddingInput = {
|
|
14
17
|
kind: 'query' | 'document';
|
|
15
18
|
text: string;
|
|
@@ -3,7 +3,9 @@ import { constants } from 'node:fs';
|
|
|
3
3
|
import { open, mkdtemp, chmod, writeFile, rm, realpath } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join, isAbsolute } from 'node:path';
|
|
5
5
|
import { runOwned } from '../../acquisition/process.js';
|
|
6
|
-
export const embeddingIdentity = { modelSha256: '92dcbea108899e5d6e30d919b0744f90d9967e80c67a4ab5503ac16d54f62eb0', librarySha256: '9526db9ee35e7eb4a5a58ac1e53330032f22a13890bd4ee01818fb380a57a362', runtime: 'LiteRT-LM@0.18.0', dimensions: 768, normalization: 'unit-l2', preprocessing: 'sips-max512-png-litert-v1', prompt: 'search-result-query-title-none-v1', protocol: 1, backend: 'cpu-2threads-f32' };
|
|
6
|
+
export const embeddingIdentity = { modelSha256: '92dcbea108899e5d6e30d919b0744f90d9967e80c67a4ab5503ac16d54f62eb0', librarySha256: '9526db9ee35e7eb4a5a58ac1e53330032f22a13890bd4ee01818fb380a57a362', runtime: 'LiteRT-LM@0.18.0', dimensions: 768, normalization: 'unit-l2', preprocessing: 'sips-max512-png-litert-v1', prompt: 'search-result-query-title-none-v1', protocol: 1, backend: 'cpu-2threads-f32', textBudget: 'utf8-1016-v1' };
|
|
7
|
+
/** The model's largest text signature is 1024 tokens; byte-fallback tokenization never yields more tokens than UTF-8 bytes. */
|
|
8
|
+
export const embeddingTextBytes = 1016;
|
|
7
9
|
export class EmbeddingError extends Error {
|
|
8
10
|
code;
|
|
9
11
|
constructor(code) {
|
|
@@ -42,7 +44,7 @@ export function encodeEmbeddingRequest(inputs) {
|
|
|
42
44
|
fail();
|
|
43
45
|
payload = Buffer.from((input.kind === 'query' ? 'task: search result | query: ' : 'title: none | text: ') + input.text);
|
|
44
46
|
kind = 1;
|
|
45
|
-
if (payload.length >
|
|
47
|
+
if (payload.length > embeddingTextBytes)
|
|
46
48
|
fail('limit-exceeded');
|
|
47
49
|
}
|
|
48
50
|
const header = Buffer.alloc(5);
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
29
|
"build": {
|
|
30
|
-
"asrSourceSha256": "
|
|
30
|
+
"asrSourceSha256": "2f8ad0eec7e46e259cba34dd3a37b09a673b553f60a63a37e54c91138db621bc",
|
|
31
31
|
"clangVersion": "Apple clang version 21.0.0 (clang-2100.1.1.101)",
|
|
32
|
-
"embeddingSourceSha256": "
|
|
32
|
+
"embeddingSourceSha256": "cbeef73ab8e45c4352444ff0c7c74f3850f3a249ca54f86c8ed89461013ecc70",
|
|
33
33
|
"swiftVersion": "Apple Swift version 6.3.3 (swiftlang-6.3.3.1.3 clang-2100.1.1.101)"
|
|
34
34
|
},
|
|
35
35
|
"minimumMacOS": "26.0",
|
|
36
|
-
"packageVersion": "0.2.
|
|
36
|
+
"packageVersion": "0.2.2",
|
|
37
37
|
"platform": "darwin",
|
|
38
38
|
"schemaVersion": 1,
|
|
39
|
-
"sourceCommit": "
|
|
39
|
+
"sourceCommit": "3e5d95f573c57ed0e3e2a2d2c496d96258bb68dd"
|
|
40
40
|
}
|