@hraness/sponge 0.2.1 → 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.
@@ -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 leave room for the fixed document prompt within 8192 UTF-8 bytes. */
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
- /** Unicode-safe chunks leave room for the fixed document prompt within 8192 UTF-8 bytes. */
25
- export function chunkDocument(text) { const chunks = []; let part = '', size = 0; for (const char of text) {
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 > 7600) {
28
- if (part.trim())
29
- chunks.push(part);
30
- part = '';
31
- size = 0;
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-v1', String(doc.id), ordinal, text]));
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-v1', input.representationId, input.ordinal, text])) !== input.derivationHash)
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 > 8192)
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": "ff8c83a1963c3d1e1a63b03279ae4fa9b7c5d914c8ec82115f886a9b23b9eb7d",
30
+ "asrSourceSha256": "2f8ad0eec7e46e259cba34dd3a37b09a673b553f60a63a37e54c91138db621bc",
31
31
  "clangVersion": "Apple clang version 21.0.0 (clang-2100.1.1.101)",
32
- "embeddingSourceSha256": "41b8381f87a32b2e9ab2fa24fcde9043463eae2117aa4b7cd758537dbb169d06",
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.1",
36
+ "packageVersion": "0.2.2",
37
37
  "platform": "darwin",
38
38
  "schemaVersion": 1,
39
- "sourceCommit": "16156a5bb4b73318da65dbf950d4c3d9f6a4aba9"
39
+ "sourceCommit": "3e5d95f573c57ed0e3e2a2d2c496d96258bb68dd"
40
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/sponge",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "A local library for captured articles and verified offline assets.",
5
5
  "repository": {
6
6
  "type": "git",