@tryhamster/gerbil 1.0.5 → 1.0.7

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.
Files changed (84) hide show
  1. package/dist/cli.mjs +10 -10
  2. package/dist/cli.mjs.map +1 -1
  3. package/dist/defaults-9komdrbY.mjs +24 -0
  4. package/dist/defaults-9komdrbY.mjs.map +1 -0
  5. package/dist/frameworks/express.d.mts +1 -1
  6. package/dist/frameworks/express.mjs +1 -1
  7. package/dist/frameworks/fastify.d.mts +1 -1
  8. package/dist/frameworks/fastify.mjs +1 -1
  9. package/dist/frameworks/hono.d.mts +1 -1
  10. package/dist/frameworks/hono.mjs +1 -1
  11. package/dist/frameworks/next.d.mts +3 -3
  12. package/dist/frameworks/next.mjs +1 -1
  13. package/dist/frameworks/react.d.mts +1 -1
  14. package/dist/frameworks/trpc.d.mts +1 -1
  15. package/dist/frameworks/trpc.mjs +1 -1
  16. package/dist/gerbil-BOsuusm3.mjs +4 -0
  17. package/dist/{gerbil-CvNqpVg0.d.mts → gerbil-BetB5xb0.d.mts} +3 -3
  18. package/dist/{gerbil-CvNqpVg0.d.mts.map → gerbil-BetB5xb0.d.mts.map} +1 -1
  19. package/dist/{gerbil-CZYYq4Sq.mjs → gerbil-D9fzoBGM.mjs} +4 -4
  20. package/dist/{gerbil-CZYYq4Sq.mjs.map → gerbil-D9fzoBGM.mjs.map} +1 -1
  21. package/dist/gpu/hooks.d.mts +2 -2
  22. package/dist/gpu/hooks.d.mts.map +1 -1
  23. package/dist/gpu/hooks.mjs +17 -4
  24. package/dist/gpu/hooks.mjs.map +1 -1
  25. package/dist/gpu/index.d.mts +1 -1
  26. package/dist/gpu/index.mjs +3 -2
  27. package/dist/{gpu-Ch2VuLdN.mjs → gpu-Bv0ZPyZO.mjs} +4 -25
  28. package/dist/gpu-Bv0ZPyZO.mjs.map +1 -0
  29. package/dist/{index-Dy_9rDd5.d.mts → index-Dgmb2kE3.d.mts} +2 -2
  30. package/dist/{index-Dy_9rDd5.d.mts.map → index-Dgmb2kE3.d.mts.map} +1 -1
  31. package/dist/{index-Nl40RdSs.d.mts → index-DukkJRMj.d.mts} +1 -1
  32. package/dist/{index-Nl40RdSs.d.mts.map → index-DukkJRMj.d.mts.map} +1 -1
  33. package/dist/index.d.mts +5 -5
  34. package/dist/index.mjs +9 -8
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/{indexeddb-store-BEylGAex.mjs → indexeddb-store-BWIMtxxH.mjs} +2 -2
  37. package/dist/{indexeddb-store-BEylGAex.mjs.map → indexeddb-store-BWIMtxxH.mjs.map} +1 -1
  38. package/dist/indexeddb-store-ClH12Xnl.mjs +4 -0
  39. package/dist/integrations/ai-sdk.d.mts +1 -1
  40. package/dist/integrations/ai-sdk.mjs +1 -1
  41. package/dist/integrations/langchain.d.mts +1 -1
  42. package/dist/integrations/langchain.mjs +1 -1
  43. package/dist/integrations/llamaindex.d.mts +1 -1
  44. package/dist/integrations/llamaindex.mjs +1 -1
  45. package/dist/integrations/mcp-client.mjs +1 -1
  46. package/dist/integrations/mcp.d.mts +3 -3
  47. package/dist/integrations/mcp.mjs +4 -4
  48. package/dist/{mcp-DUgk28Kp.mjs → mcp-Cobm_wmI.mjs} +3 -3
  49. package/dist/{mcp-DUgk28Kp.mjs.map → mcp-Cobm_wmI.mjs.map} +1 -1
  50. package/dist/memory/index.d.mts +2 -2
  51. package/dist/memory/index.mjs +4 -4
  52. package/dist/memory-D1P7Tmda.mjs +4 -0
  53. package/dist/{memory-Deo99oKh.mjs → memory-DVN0MnIG.mjs} +2 -2
  54. package/dist/{memory-Deo99oKh.mjs.map → memory-DVN0MnIG.mjs.map} +1 -1
  55. package/dist/{memory-wgo2ZK3M.mjs → memory-Dj0J1v88.mjs} +2 -2
  56. package/dist/{memory-wgo2ZK3M.mjs.map → memory-Dj0J1v88.mjs.map} +1 -1
  57. package/dist/{moonshine-stt-DR0-JbVW.mjs → moonshine-stt-C2PR8fM4.mjs} +66 -9
  58. package/dist/{moonshine-stt-DR0-JbVW.mjs.map → moonshine-stt-C2PR8fM4.mjs.map} +1 -1
  59. package/dist/moonshine-stt-D0EXyr4g.mjs +4 -0
  60. package/dist/{one-liner-sTDG9Hum.mjs → one-liner-rqFVBbNs.mjs} +2 -2
  61. package/dist/{one-liner-sTDG9Hum.mjs.map → one-liner-rqFVBbNs.mjs.map} +1 -1
  62. package/dist/repl-BJVyb3Qf.mjs +9 -0
  63. package/dist/skills/index.d.mts +14 -14
  64. package/dist/skills/index.d.mts.map +1 -1
  65. package/dist/skills/index.mjs +3 -3
  66. package/dist/{skills-CEUuOUlm.mjs → skills-c8rCxj2n.mjs} +2 -2
  67. package/dist/{skills-CEUuOUlm.mjs.map → skills-c8rCxj2n.mjs.map} +1 -1
  68. package/dist/{tools-DbbNCJ5y.mjs → tools-DQ1mPUw5.mjs} +1 -1
  69. package/dist/{tools-DbbNCJ5y.mjs.map → tools-DQ1mPUw5.mjs.map} +1 -1
  70. package/dist/{types-D7kn-0i2.d.mts → types-DQBe2lFo.d.mts} +1 -1
  71. package/dist/{types-D7kn-0i2.d.mts.map → types-DQBe2lFo.d.mts.map} +1 -1
  72. package/dist/{types-Bxwe_uS7.d.mts → types-LlyYILII.d.mts} +1 -1
  73. package/dist/{types-Bxwe_uS7.d.mts.map → types-LlyYILII.d.mts.map} +1 -1
  74. package/dist/{vector-C1U9vVVS.mjs → vector-B0panuy6.mjs} +1 -1
  75. package/dist/{vector-C1U9vVVS.mjs.map → vector-B0panuy6.mjs.map} +1 -1
  76. package/package.json +1 -1
  77. package/dist/gerbil-BgppGzKa.mjs +0 -4
  78. package/dist/gpu-Ch2VuLdN.mjs.map +0 -1
  79. package/dist/indexeddb-store-ClSHQxwz.mjs +0 -4
  80. package/dist/memory-CGRo6zY5.mjs +0 -4
  81. package/dist/moonshine-stt-DFIP4tIb.mjs +0 -4
  82. package/dist/repl-njlFQs4-.mjs +0 -9
  83. /package/dist/{auto-update-DNbr3GLz.mjs → auto-update-BVaLXcDE.mjs} +0 -0
  84. /package/dist/{microphone-sj_bnRm-.mjs → microphone-Bqmoz9_K.mjs} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"memory-wgo2ZK3M.mjs","names":["chunks: string[]","candidates: { item: MemoryRecord; vector: Float32Array }[]","ranked: Scored<MemoryRecord>[]","out: MemoryRecord[]","records: MemoryRecord[]","packed: MemoryRecord[]"],"sources":["../src/memory/chunking.ts","../src/memory/redaction.ts","../src/memory/stores/memory-store.ts","../src/memory/tokens.ts","../src/memory/memory.ts"],"sourcesContent":["/**\n * Document chunking: split long text into overlapping windows before\n * embedding so retrieval can target relevant passages.\n */\n\nimport type { ChunkOptions } from \"./types.js\";\n\nconst DEFAULT_CHUNK_SIZE = 1000;\nconst DEFAULT_OVERLAP = 200;\n\n/**\n * Split `text` into overlapping character windows.\n *\n * Chunks are `chunkSize` characters with `overlap` characters shared between\n * consecutive chunks, which preserves context across boundaries. Whitespace\n * is trimmed and empty chunks are dropped. Short text returns a single chunk.\n *\n * @throws if `overlap` is not less than `chunkSize` (would loop forever).\n */\nexport function chunkText(text: string, options: ChunkOptions = {}): string[] {\n const chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;\n const overlap = options.overlap ?? DEFAULT_OVERLAP;\n\n if (chunkSize <= 0) {\n throw new Error(\"chunkSize must be greater than 0\");\n }\n if (overlap < 0 || overlap >= chunkSize) {\n throw new Error(\"overlap must be >= 0 and < chunkSize\");\n }\n\n const trimmed = text.trim();\n if (trimmed.length <= chunkSize) {\n return trimmed.length > 0 ? [trimmed] : [];\n }\n\n const stride = chunkSize - overlap;\n const chunks: string[] = [];\n for (let start = 0; start < trimmed.length; start += stride) {\n const chunk = trimmed.slice(start, start + chunkSize).trim();\n if (chunk.length > 0) {\n chunks.push(chunk);\n }\n if (start + chunkSize >= trimmed.length) {\n break;\n }\n }\n return chunks;\n}\n","/**\n * Privacy redaction applied to memory text on write.\n */\n\nimport type { Redactor } from \"./types.js\";\n\nconst REDACTION_PLACEHOLDER = \"[REDACTED]\";\n\n/**\n * Apply a {@link Redactor} to `text`.\n *\n * - A {@link RegExp} replaces all matches with `[REDACTED]` (the `g` flag is\n * forced so replacement is global regardless of the supplied flags).\n * - A function is invoked and its return value used.\n * - `undefined` returns the text unchanged.\n */\nexport function applyRedaction(text: string, redact?: Redactor): string {\n if (!redact) {\n return text;\n }\n if (typeof redact === \"function\") {\n return redact(text);\n }\n const global = redact.global ? redact : new RegExp(redact.source, `${redact.flags}g`);\n return text.replace(global, REDACTION_PLACEHOLDER);\n}\n","/**\n * In-memory {@link MemoryStore} — the default backend.\n *\n * Works in both Node and the browser. Holds records in a `Map` and performs\n * a brute-force cosine top-k scan on search. Records are expected to carry\n * pre-normalized embeddings (the {@link Memory} facade normalizes on insert).\n */\n\nimport { matchesFilter } from \"../serialize.js\";\nimport type {\n MemoryRecord,\n MemorySearchResult,\n MemoryStore,\n MetadataFilter,\n StoreSearchOptions,\n} from \"../types.js\";\nimport { type Scored, topK } from \"../vector.js\";\n\nconst DEFAULT_K = 5;\n\n/** Brute-force, embedding-agnostic in-memory vector store. */\nexport class InMemoryStore implements MemoryStore {\n private readonly records = new Map<string, MemoryRecord>();\n\n add(record: MemoryRecord): Promise<void> {\n this.records.set(record.id, record);\n return Promise.resolve();\n }\n\n addMany(records: MemoryRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n return Promise.resolve();\n }\n\n get(id: string): Promise<MemoryRecord | undefined> {\n return Promise.resolve(this.records.get(id));\n }\n\n search(\n queryVector: Float32Array,\n options: StoreSearchOptions = {},\n ): Promise<MemorySearchResult[]> {\n const k = options.k ?? DEFAULT_K;\n const candidates: { item: MemoryRecord; vector: Float32Array }[] = [];\n for (const record of this.records.values()) {\n if (!record.embedding) {\n continue;\n }\n if (!matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n candidates.push({ item: record, vector: record.embedding });\n }\n const ranked: Scored<MemoryRecord>[] = topK(queryVector, candidates, k, options.minScore);\n return Promise.resolve(ranked.map((entry) => ({ record: entry.item, score: entry.score })));\n }\n\n delete(id: string): Promise<boolean> {\n return Promise.resolve(this.records.delete(id));\n }\n\n list(filter?: MetadataFilter): Promise<MemoryRecord[]> {\n const out: MemoryRecord[] = [];\n for (const record of this.records.values()) {\n if (matchesFilter(record.metadata, filter)) {\n out.push(record);\n }\n }\n return Promise.resolve(out);\n }\n\n clear(): Promise<void> {\n this.records.clear();\n return Promise.resolve();\n }\n\n size(): Promise<number> {\n return Promise.resolve(this.records.size);\n }\n}\n\n/** Create an {@link InMemoryStore}. */\nexport function createInMemoryStore(): MemoryStore {\n return new InMemoryStore();\n}\n","/**\n * Approximate token counting for context packing.\n *\n * We deliberately avoid a real tokenizer dependency here. The heuristic is\n * the widely used \"~4 characters per token\" rule for English-ish text, with\n * a small floor so very short strings still cost at least one token. This is\n * an estimate used only for budgeting — slightly conservative is fine, since\n * the goal is to stay under a model's context window, not to be exact.\n */\n\nconst CHARS_PER_TOKEN = 4;\n\n/**\n * Estimate the number of tokens in `text`.\n *\n * Uses the ~4-chars-per-token heuristic. Returns 0 for empty strings.\n */\nexport function estimateTokens(text: string): number {\n if (text.length === 0) {\n return 0;\n }\n return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));\n}\n","/**\n * The {@link Memory} facade: the small, obvious public API over a pluggable\n * {@link MemoryStore} and an injected {@link Embedder}.\n *\n * Lifecycle of a write: redact -> (optional) chunk -> embed -> normalize ->\n * store. Reads embed the query once and delegate cosine top-k to the store.\n * {@link Memory.recall} adds token-budgeted context packing on top of search.\n */\n\nimport { chunkText } from \"./chunking.js\";\nimport { applyRedaction } from \"./redaction.js\";\nimport { deserializeRecord, serializeRecord } from \"./serialize.js\";\nimport { createInMemoryStore } from \"./stores/memory-store.js\";\nimport { estimateTokens } from \"./tokens.js\";\nimport type {\n AddOptions,\n ChunkOptions,\n Embedder,\n MemoryExport,\n MemoryOptions,\n MemoryRecord,\n MemorySearchResult,\n MemoryStore,\n MetadataFilter,\n RecallOptions,\n RecallResult,\n Redactor,\n SearchOptions,\n} from \"./types.js\";\nimport { normalize } from \"./vector.js\";\n\nconst DEFAULT_RECALL_K = 20;\nconst DEFAULT_TOKEN_BUDGET = 1024;\nconst DEFAULT_SEPARATOR = \"\\n\\n\";\n\nfunction generateId(): string {\n // crypto.randomUUID exists in modern Node (>=16) and browsers.\n const cryptoObj = globalThis.crypto;\n if (cryptoObj?.randomUUID) {\n return cryptoObj.randomUUID();\n }\n return `mem-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * On-device persistent memory with semantic recall.\n *\n * Construct via {@link createMemory}. Engine-agnostic: bring any\n * {@link Embedder} (Gerbil's native embeddings by default) and any\n * {@link MemoryStore} backend.\n */\nexport class Memory {\n private readonly embedder: Embedder;\n private readonly store: MemoryStore;\n private readonly redact?: Redactor;\n private readonly defaultChunk?: ChunkOptions;\n\n constructor(options: MemoryOptions) {\n if (typeof options.embed !== \"function\") {\n throw new Error(\"createMemory requires an `embed` function\");\n }\n this.embedder = options.embed;\n this.store = options.store ?? createInMemoryStore();\n this.redact = options.redact;\n this.defaultChunk = options.chunk;\n }\n\n /** The underlying store, for advanced use (custom queries, swapping). */\n get backend(): MemoryStore {\n return this.store;\n }\n\n /**\n * Add text to memory. Long text can be split into overlapping chunks\n * (one record per chunk). Returns the ids of the created record(s).\n */\n async add(text: string, options: AddOptions = {}): Promise<string[]> {\n const redacted = applyRedaction(text, this.redact);\n\n const chunkConfig = this.resolveChunking(options.chunk);\n const pieces = chunkConfig ? chunkText(redacted, chunkConfig) : [redacted];\n const nonEmpty = pieces.filter((piece) => piece.length > 0);\n if (nonEmpty.length === 0) {\n return [];\n }\n\n if (options.id && nonEmpty.length > 1) {\n throw new Error(\"Cannot use an explicit `id` when text is split into multiple chunks\");\n }\n\n const vectors = await this.embedder(nonEmpty);\n const createdAt = Date.now();\n const metadata = options.metadata ?? {};\n const records: MemoryRecord[] = nonEmpty.map((piece, index) => ({\n id: index === 0 && options.id ? options.id : generateId(),\n text: piece,\n embedding: normalize(vectors[index]),\n metadata,\n createdAt,\n }));\n\n await this.store.addMany(records);\n return records.map((record) => record.id);\n }\n\n private resolveChunking(chunk: AddOptions[\"chunk\"]): ChunkOptions | undefined {\n if (chunk === true) {\n return this.defaultChunk ?? {};\n }\n if (chunk && typeof chunk === \"object\") {\n return chunk;\n }\n return;\n }\n\n /** Fetch a record by id. */\n get(id: string): Promise<MemoryRecord | undefined> {\n return this.store.get(id);\n }\n\n /** Semantic search: returns the top-k records by cosine similarity. */\n async search(query: string, options: SearchOptions = {}): Promise<MemorySearchResult[]> {\n const [vector] = await this.embedder([query]);\n return this.store.search(normalize(vector), {\n k: options.k,\n filter: options.filter,\n minScore: options.minScore,\n });\n }\n\n /**\n * Retrieve relevant memories and greedily pack them into a token-budgeted\n * context block, highest-scoring first, stopping before the budget is\n * exceeded. This is the per-turn context-rebuild step that turns the store\n * into agent memory. Token counts are approximate (see {@link estimateTokens}).\n */\n async recall(query: string, options: RecallOptions = {}): Promise<RecallResult> {\n const budget = options.tokenBudget ?? DEFAULT_TOKEN_BUDGET;\n const separator = options.separator ?? DEFAULT_SEPARATOR;\n const candidates = await this.search(query, {\n k: options.k ?? DEFAULT_RECALL_K,\n filter: options.filter,\n minScore: options.minScore,\n });\n\n const separatorTokens = estimateTokens(separator);\n const packed: MemoryRecord[] = [];\n let tokensUsed = 0;\n for (const { record } of candidates) {\n const recordTokens = estimateTokens(record.text);\n const withSeparator = packed.length === 0 ? recordTokens : recordTokens + separatorTokens;\n if (tokensUsed + withSeparator > budget) {\n continue; // Try smaller later candidates rather than stop outright.\n }\n packed.push(record);\n tokensUsed += withSeparator;\n }\n\n return {\n context: packed.map((record) => record.text).join(separator),\n records: packed,\n tokensUsed,\n };\n }\n\n /** Delete a record by id. Returns `true` if it existed. */\n delete(id: string): Promise<boolean> {\n return this.store.delete(id);\n }\n\n /** List all records, optionally filtered by metadata. */\n list(filter?: MetadataFilter): Promise<MemoryRecord[]> {\n return this.store.list(filter);\n }\n\n /** Remove all records. */\n clear(): Promise<void> {\n return this.store.clear();\n }\n\n /** Total number of stored records. */\n size(): Promise<number> {\n return this.store.size();\n }\n\n /** Export the full corpus as a JSON-serializable snapshot. */\n async export(): Promise<MemoryExport> {\n const records = await this.store.list();\n return {\n version: 1,\n records: records.map(serializeRecord),\n };\n }\n\n /**\n * Import records from a snapshot produced by {@link Memory.export}.\n * Existing records with the same id are overwritten.\n */\n async import(snapshot: MemoryExport): Promise<void> {\n const records = snapshot.records.map(deserializeRecord);\n await this.store.addMany(records);\n }\n}\n\n/**\n * Create a {@link Memory} instance.\n *\n * @example\n * ```ts\n * const mem = createMemory({ embed, store });\n * await mem.add(\"Paris is the capital of France\", { metadata: { topic: \"geo\" } });\n * const hits = await mem.search(\"French capital\", { k: 3 });\n * const { context } = await mem.recall(\"French capital\", { tokenBudget: 512 });\n * ```\n */\nexport function createMemory(options: MemoryOptions): Memory {\n return new Memory(options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory-Dj0J1v88.mjs","names":["chunks: string[]","candidates: { item: MemoryRecord; vector: Float32Array }[]","ranked: Scored<MemoryRecord>[]","out: MemoryRecord[]","records: MemoryRecord[]","packed: MemoryRecord[]"],"sources":["../src/memory/chunking.ts","../src/memory/redaction.ts","../src/memory/stores/memory-store.ts","../src/memory/tokens.ts","../src/memory/memory.ts"],"sourcesContent":["/**\n * Document chunking: split long text into overlapping windows before\n * embedding so retrieval can target relevant passages.\n */\n\nimport type { ChunkOptions } from \"./types.js\";\n\nconst DEFAULT_CHUNK_SIZE = 1000;\nconst DEFAULT_OVERLAP = 200;\n\n/**\n * Split `text` into overlapping character windows.\n *\n * Chunks are `chunkSize` characters with `overlap` characters shared between\n * consecutive chunks, which preserves context across boundaries. Whitespace\n * is trimmed and empty chunks are dropped. Short text returns a single chunk.\n *\n * @throws if `overlap` is not less than `chunkSize` (would loop forever).\n */\nexport function chunkText(text: string, options: ChunkOptions = {}): string[] {\n const chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;\n const overlap = options.overlap ?? DEFAULT_OVERLAP;\n\n if (chunkSize <= 0) {\n throw new Error(\"chunkSize must be greater than 0\");\n }\n if (overlap < 0 || overlap >= chunkSize) {\n throw new Error(\"overlap must be >= 0 and < chunkSize\");\n }\n\n const trimmed = text.trim();\n if (trimmed.length <= chunkSize) {\n return trimmed.length > 0 ? [trimmed] : [];\n }\n\n const stride = chunkSize - overlap;\n const chunks: string[] = [];\n for (let start = 0; start < trimmed.length; start += stride) {\n const chunk = trimmed.slice(start, start + chunkSize).trim();\n if (chunk.length > 0) {\n chunks.push(chunk);\n }\n if (start + chunkSize >= trimmed.length) {\n break;\n }\n }\n return chunks;\n}\n","/**\n * Privacy redaction applied to memory text on write.\n */\n\nimport type { Redactor } from \"./types.js\";\n\nconst REDACTION_PLACEHOLDER = \"[REDACTED]\";\n\n/**\n * Apply a {@link Redactor} to `text`.\n *\n * - A {@link RegExp} replaces all matches with `[REDACTED]` (the `g` flag is\n * forced so replacement is global regardless of the supplied flags).\n * - A function is invoked and its return value used.\n * - `undefined` returns the text unchanged.\n */\nexport function applyRedaction(text: string, redact?: Redactor): string {\n if (!redact) {\n return text;\n }\n if (typeof redact === \"function\") {\n return redact(text);\n }\n const global = redact.global ? redact : new RegExp(redact.source, `${redact.flags}g`);\n return text.replace(global, REDACTION_PLACEHOLDER);\n}\n","/**\n * In-memory {@link MemoryStore} — the default backend.\n *\n * Works in both Node and the browser. Holds records in a `Map` and performs\n * a brute-force cosine top-k scan on search. Records are expected to carry\n * pre-normalized embeddings (the {@link Memory} facade normalizes on insert).\n */\n\nimport { matchesFilter } from \"../serialize.js\";\nimport type {\n MemoryRecord,\n MemorySearchResult,\n MemoryStore,\n MetadataFilter,\n StoreSearchOptions,\n} from \"../types.js\";\nimport { type Scored, topK } from \"../vector.js\";\n\nconst DEFAULT_K = 5;\n\n/** Brute-force, embedding-agnostic in-memory vector store. */\nexport class InMemoryStore implements MemoryStore {\n private readonly records = new Map<string, MemoryRecord>();\n\n add(record: MemoryRecord): Promise<void> {\n this.records.set(record.id, record);\n return Promise.resolve();\n }\n\n addMany(records: MemoryRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n return Promise.resolve();\n }\n\n get(id: string): Promise<MemoryRecord | undefined> {\n return Promise.resolve(this.records.get(id));\n }\n\n search(\n queryVector: Float32Array,\n options: StoreSearchOptions = {},\n ): Promise<MemorySearchResult[]> {\n const k = options.k ?? DEFAULT_K;\n const candidates: { item: MemoryRecord; vector: Float32Array }[] = [];\n for (const record of this.records.values()) {\n if (!record.embedding) {\n continue;\n }\n if (!matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n candidates.push({ item: record, vector: record.embedding });\n }\n const ranked: Scored<MemoryRecord>[] = topK(queryVector, candidates, k, options.minScore);\n return Promise.resolve(ranked.map((entry) => ({ record: entry.item, score: entry.score })));\n }\n\n delete(id: string): Promise<boolean> {\n return Promise.resolve(this.records.delete(id));\n }\n\n list(filter?: MetadataFilter): Promise<MemoryRecord[]> {\n const out: MemoryRecord[] = [];\n for (const record of this.records.values()) {\n if (matchesFilter(record.metadata, filter)) {\n out.push(record);\n }\n }\n return Promise.resolve(out);\n }\n\n clear(): Promise<void> {\n this.records.clear();\n return Promise.resolve();\n }\n\n size(): Promise<number> {\n return Promise.resolve(this.records.size);\n }\n}\n\n/** Create an {@link InMemoryStore}. */\nexport function createInMemoryStore(): MemoryStore {\n return new InMemoryStore();\n}\n","/**\n * Approximate token counting for context packing.\n *\n * We deliberately avoid a real tokenizer dependency here. The heuristic is\n * the widely used \"~4 characters per token\" rule for English-ish text, with\n * a small floor so very short strings still cost at least one token. This is\n * an estimate used only for budgeting — slightly conservative is fine, since\n * the goal is to stay under a model's context window, not to be exact.\n */\n\nconst CHARS_PER_TOKEN = 4;\n\n/**\n * Estimate the number of tokens in `text`.\n *\n * Uses the ~4-chars-per-token heuristic. Returns 0 for empty strings.\n */\nexport function estimateTokens(text: string): number {\n if (text.length === 0) {\n return 0;\n }\n return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));\n}\n","/**\n * The {@link Memory} facade: the small, obvious public API over a pluggable\n * {@link MemoryStore} and an injected {@link Embedder}.\n *\n * Lifecycle of a write: redact -> (optional) chunk -> embed -> normalize ->\n * store. Reads embed the query once and delegate cosine top-k to the store.\n * {@link Memory.recall} adds token-budgeted context packing on top of search.\n */\n\nimport { chunkText } from \"./chunking.js\";\nimport { applyRedaction } from \"./redaction.js\";\nimport { deserializeRecord, serializeRecord } from \"./serialize.js\";\nimport { createInMemoryStore } from \"./stores/memory-store.js\";\nimport { estimateTokens } from \"./tokens.js\";\nimport type {\n AddOptions,\n ChunkOptions,\n Embedder,\n MemoryExport,\n MemoryOptions,\n MemoryRecord,\n MemorySearchResult,\n MemoryStore,\n MetadataFilter,\n RecallOptions,\n RecallResult,\n Redactor,\n SearchOptions,\n} from \"./types.js\";\nimport { normalize } from \"./vector.js\";\n\nconst DEFAULT_RECALL_K = 20;\nconst DEFAULT_TOKEN_BUDGET = 1024;\nconst DEFAULT_SEPARATOR = \"\\n\\n\";\n\nfunction generateId(): string {\n // crypto.randomUUID exists in modern Node (>=16) and browsers.\n const cryptoObj = globalThis.crypto;\n if (cryptoObj?.randomUUID) {\n return cryptoObj.randomUUID();\n }\n return `mem-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * On-device persistent memory with semantic recall.\n *\n * Construct via {@link createMemory}. Engine-agnostic: bring any\n * {@link Embedder} (Gerbil's native embeddings by default) and any\n * {@link MemoryStore} backend.\n */\nexport class Memory {\n private readonly embedder: Embedder;\n private readonly store: MemoryStore;\n private readonly redact?: Redactor;\n private readonly defaultChunk?: ChunkOptions;\n\n constructor(options: MemoryOptions) {\n if (typeof options.embed !== \"function\") {\n throw new Error(\"createMemory requires an `embed` function\");\n }\n this.embedder = options.embed;\n this.store = options.store ?? createInMemoryStore();\n this.redact = options.redact;\n this.defaultChunk = options.chunk;\n }\n\n /** The underlying store, for advanced use (custom queries, swapping). */\n get backend(): MemoryStore {\n return this.store;\n }\n\n /**\n * Add text to memory. Long text can be split into overlapping chunks\n * (one record per chunk). Returns the ids of the created record(s).\n */\n async add(text: string, options: AddOptions = {}): Promise<string[]> {\n const redacted = applyRedaction(text, this.redact);\n\n const chunkConfig = this.resolveChunking(options.chunk);\n const pieces = chunkConfig ? chunkText(redacted, chunkConfig) : [redacted];\n const nonEmpty = pieces.filter((piece) => piece.length > 0);\n if (nonEmpty.length === 0) {\n return [];\n }\n\n if (options.id && nonEmpty.length > 1) {\n throw new Error(\"Cannot use an explicit `id` when text is split into multiple chunks\");\n }\n\n const vectors = await this.embedder(nonEmpty);\n const createdAt = Date.now();\n const metadata = options.metadata ?? {};\n const records: MemoryRecord[] = nonEmpty.map((piece, index) => ({\n id: index === 0 && options.id ? options.id : generateId(),\n text: piece,\n embedding: normalize(vectors[index]),\n metadata,\n createdAt,\n }));\n\n await this.store.addMany(records);\n return records.map((record) => record.id);\n }\n\n private resolveChunking(chunk: AddOptions[\"chunk\"]): ChunkOptions | undefined {\n if (chunk === true) {\n return this.defaultChunk ?? {};\n }\n if (chunk && typeof chunk === \"object\") {\n return chunk;\n }\n return;\n }\n\n /** Fetch a record by id. */\n get(id: string): Promise<MemoryRecord | undefined> {\n return this.store.get(id);\n }\n\n /** Semantic search: returns the top-k records by cosine similarity. */\n async search(query: string, options: SearchOptions = {}): Promise<MemorySearchResult[]> {\n const [vector] = await this.embedder([query]);\n return this.store.search(normalize(vector), {\n k: options.k,\n filter: options.filter,\n minScore: options.minScore,\n });\n }\n\n /**\n * Retrieve relevant memories and greedily pack them into a token-budgeted\n * context block, highest-scoring first, stopping before the budget is\n * exceeded. This is the per-turn context-rebuild step that turns the store\n * into agent memory. Token counts are approximate (see {@link estimateTokens}).\n */\n async recall(query: string, options: RecallOptions = {}): Promise<RecallResult> {\n const budget = options.tokenBudget ?? DEFAULT_TOKEN_BUDGET;\n const separator = options.separator ?? DEFAULT_SEPARATOR;\n const candidates = await this.search(query, {\n k: options.k ?? DEFAULT_RECALL_K,\n filter: options.filter,\n minScore: options.minScore,\n });\n\n const separatorTokens = estimateTokens(separator);\n const packed: MemoryRecord[] = [];\n let tokensUsed = 0;\n for (const { record } of candidates) {\n const recordTokens = estimateTokens(record.text);\n const withSeparator = packed.length === 0 ? recordTokens : recordTokens + separatorTokens;\n if (tokensUsed + withSeparator > budget) {\n continue; // Try smaller later candidates rather than stop outright.\n }\n packed.push(record);\n tokensUsed += withSeparator;\n }\n\n return {\n context: packed.map((record) => record.text).join(separator),\n records: packed,\n tokensUsed,\n };\n }\n\n /** Delete a record by id. Returns `true` if it existed. */\n delete(id: string): Promise<boolean> {\n return this.store.delete(id);\n }\n\n /** List all records, optionally filtered by metadata. */\n list(filter?: MetadataFilter): Promise<MemoryRecord[]> {\n return this.store.list(filter);\n }\n\n /** Remove all records. */\n clear(): Promise<void> {\n return this.store.clear();\n }\n\n /** Total number of stored records. */\n size(): Promise<number> {\n return this.store.size();\n }\n\n /** Export the full corpus as a JSON-serializable snapshot. */\n async export(): Promise<MemoryExport> {\n const records = await this.store.list();\n return {\n version: 1,\n records: records.map(serializeRecord),\n };\n }\n\n /**\n * Import records from a snapshot produced by {@link Memory.export}.\n * Existing records with the same id are overwritten.\n */\n async import(snapshot: MemoryExport): Promise<void> {\n const records = snapshot.records.map(deserializeRecord);\n await this.store.addMany(records);\n }\n}\n\n/**\n * Create a {@link Memory} instance.\n *\n * @example\n * ```ts\n * const mem = createMemory({ embed, store });\n * await mem.add(\"Paris is the capital of France\", { metadata: { topic: \"geo\" } });\n * const hits = await mem.search(\"French capital\", { k: 3 });\n * const { context } = await mem.recall(\"French capital\", { tokenBudget: 512 });\n * ```\n */\nexport function createMemory(options: MemoryOptions): Memory {\n return new Memory(options);\n}\n"],"mappings":"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"}
@@ -9383,6 +9383,28 @@ function idbAvailable() {
9383
9383
  return typeof indexedDB !== "undefined";
9384
9384
  }
9385
9385
  let _dbPromise = null;
9386
+ /**
9387
+ * Per-op watchdog. iOS Safari can silently WEDGE an IndexedDB transaction after
9388
+ * a large (~400 MB) write session or under quota pressure — it fires NONE of
9389
+ * success/error/abort, so an awaiting caller hangs forever. That presents as the
9390
+ * load freezing at "reading model.safetensors header" (the first storage op
9391
+ * after that label) and is the #1 cause of the on-device load hang. Every op
9392
+ * races this timeout and degrades to its fallback (read → miss → network; write
9393
+ * → false → heap) instead of hanging.
9394
+ */
9395
+ const IDB_OP_TIMEOUT_MS = 12e3;
9396
+ /**
9397
+ * Drop the memoized connection (and close it) so the NEXT op re-opens a fresh
9398
+ * one. A wedged/poisoned connection otherwise persists for the page's lifetime —
9399
+ * an in-page retry (no reload) reuses it and hangs again. Closing also aborts
9400
+ * the wedged transaction.
9401
+ */
9402
+ function poisonConnection(db) {
9403
+ _dbPromise = null;
9404
+ try {
9405
+ db?.close();
9406
+ } catch {}
9407
+ }
9386
9408
  function openDB() {
9387
9409
  if (_dbPromise) return _dbPromise;
9388
9410
  _dbPromise = new Promise((resolve) => {
@@ -9396,7 +9418,14 @@ function openDB() {
9396
9418
  const db = req.result;
9397
9419
  if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE);
9398
9420
  };
9399
- req.onsuccess = () => resolve(req.result);
9421
+ req.onsuccess = () => {
9422
+ const db = req.result;
9423
+ db.onversionchange = () => poisonConnection(db);
9424
+ db.onclose = () => {
9425
+ _dbPromise = null;
9426
+ };
9427
+ resolve(db);
9428
+ };
9400
9429
  req.onerror = () => resolve(null);
9401
9430
  req.onblocked = () => resolve(null);
9402
9431
  } catch {
@@ -9405,21 +9434,32 @@ function openDB() {
9405
9434
  });
9406
9435
  return _dbPromise;
9407
9436
  }
9408
- /** A single transaction operation, promisified. Resolves the fallback on any error. */
9437
+ /** A single transaction operation, promisified. Resolves the fallback on any
9438
+ * error OR if the op wedges past IDB_OP_TIMEOUT_MS (and then poisons the
9439
+ * connection so the next op re-opens fresh). */
9409
9440
  function tx(mode, run, fallback) {
9410
9441
  return openDB().then((db) => new Promise((resolve) => {
9411
9442
  if (!db) {
9412
9443
  resolve(fallback);
9413
9444
  return;
9414
9445
  }
9446
+ let settled = false;
9447
+ const finish = (value, poisoned) => {
9448
+ if (settled) return;
9449
+ settled = true;
9450
+ clearTimeout(timer);
9451
+ if (poisoned) poisonConnection(db);
9452
+ resolve(value);
9453
+ };
9454
+ const timer = setTimeout(() => finish(fallback, true), IDB_OP_TIMEOUT_MS);
9415
9455
  try {
9416
9456
  const t = db.transaction(STORE, mode);
9417
9457
  const req = run(t.objectStore(STORE));
9418
- req.onsuccess = () => resolve(req.result ?? fallback);
9419
- req.onerror = () => resolve(fallback);
9420
- t.onabort = () => resolve(fallback);
9458
+ req.onsuccess = () => finish(req.result ?? fallback, false);
9459
+ req.onerror = () => finish(fallback, true);
9460
+ t.onabort = () => finish(fallback, true);
9421
9461
  } catch {
9422
- resolve(fallback);
9462
+ finish(fallback, true);
9423
9463
  }
9424
9464
  }));
9425
9465
  }
@@ -9662,6 +9702,8 @@ self.onmessage = async (ev) => {
9662
9702
  * any handshake fails the whole OPFS path is marked unavailable so callers fall
9663
9703
  * back to CacheStorage cleanly.
9664
9704
  */
9705
+ /** Per-op watchdog for the OPFS worker — mirrors IDB_OP_TIMEOUT_MS. */
9706
+ const OPFS_OP_TIMEOUT_MS = 1e4;
9665
9707
  var OpfsClient = class {
9666
9708
  worker = null;
9667
9709
  blobUrl = null;
@@ -9703,9 +9745,23 @@ var OpfsClient = class {
9703
9745
  if (!worker) return Promise.reject(/* @__PURE__ */ new Error("OPFS unavailable"));
9704
9746
  const id = this.nextId++;
9705
9747
  return new Promise((resolve, reject) => {
9748
+ const timer = setTimeout(() => {
9749
+ if (!this.pending.delete(id)) return;
9750
+ try {
9751
+ worker.terminate();
9752
+ } catch {}
9753
+ if (this.worker === worker) this.worker = null;
9754
+ resolve({});
9755
+ }, OPFS_OP_TIMEOUT_MS);
9706
9756
  this.pending.set(id, {
9707
- resolve,
9708
- reject
9757
+ resolve: (v) => {
9758
+ clearTimeout(timer);
9759
+ resolve(v);
9760
+ },
9761
+ reject: (e) => {
9762
+ clearTimeout(timer);
9763
+ reject(e);
9764
+ }
9709
9765
  });
9710
9766
  try {
9711
9767
  if (buf) worker.postMessage({
@@ -9720,6 +9776,7 @@ var OpfsClient = class {
9720
9776
  name
9721
9777
  });
9722
9778
  } catch (e) {
9779
+ clearTimeout(timer);
9723
9780
  this.pending.delete(id);
9724
9781
  reject(e instanceof Error ? e : new Error(String(e)));
9725
9782
  }
@@ -11974,4 +12031,4 @@ function selectGraphWeights(graph, weights) {
11974
12031
 
11975
12032
  //#endregion
11976
12033
  export { loadMoonshine as a, MATMUL_BIAS_F16C_SPEC as c, createStorageBuffer as d, createUniformBuffer as f, verifyGPU as g, initGPU as h, loadModel as i, clearPipelineCache as l, getOrCreatePipeline as m, MoonshineEncoderExecutor as n, Executor as o, destroyBuffers as p, loadKaniTTS as r, KERNEL_REGISTRY as s, MoonshineSTT as t, createBindGroup as u };
11977
- //# sourceMappingURL=moonshine-stt-DR0-JbVW.mjs.map
12034
+ //# sourceMappingURL=moonshine-stt-C2PR8fM4.mjs.map