mem-os-python 1.6.27__py3-none-any.whl

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 (253) hide show
  1. mem_os_python-1.6.27.dist-info/METADATA +1032 -0
  2. mem_os_python-1.6.27.dist-info/RECORD +253 -0
  3. mem_os_python-1.6.27.dist-info/WHEEL +4 -0
  4. mem_os_python-1.6.27.dist-info/entry_points.txt +2 -0
  5. mem_os_python-1.6.27.dist-info/licenses/LICENSE +21 -0
  6. memos/__init__.py +16 -0
  7. memos/_js/adapters/anthropic.d.ts +279 -0
  8. memos/_js/adapters/anthropic.d.ts.map +1 -0
  9. memos/_js/adapters/anthropic.js +348 -0
  10. memos/_js/adapters/anthropic.js.map +1 -0
  11. memos/_js/adapters/base.d.ts +10 -0
  12. memos/_js/adapters/base.d.ts.map +1 -0
  13. memos/_js/adapters/base.js +2 -0
  14. memos/_js/adapters/base.js.map +1 -0
  15. memos/_js/adapters/openai.d.ts +264 -0
  16. memos/_js/adapters/openai.d.ts.map +1 -0
  17. memos/_js/adapters/openai.js +342 -0
  18. memos/_js/adapters/openai.js.map +1 -0
  19. memos/_js/apps/explorer.d.ts +244 -0
  20. memos/_js/apps/explorer.d.ts.map +1 -0
  21. memos/_js/apps/explorer.js +738 -0
  22. memos/_js/apps/explorer.js.map +1 -0
  23. memos/_js/citations.d.ts +65 -0
  24. memos/_js/citations.d.ts.map +1 -0
  25. memos/_js/citations.js +143 -0
  26. memos/_js/citations.js.map +1 -0
  27. memos/_js/cli-browse.d.ts +3 -0
  28. memos/_js/cli-browse.d.ts.map +1 -0
  29. memos/_js/cli-browse.js +129 -0
  30. memos/_js/cli-browse.js.map +1 -0
  31. memos/_js/cli-completion.d.ts +31 -0
  32. memos/_js/cli-completion.d.ts.map +1 -0
  33. memos/_js/cli-completion.js +340 -0
  34. memos/_js/cli-completion.js.map +1 -0
  35. memos/_js/cli-help.d.ts +18 -0
  36. memos/_js/cli-help.d.ts.map +1 -0
  37. memos/_js/cli-help.js +357 -0
  38. memos/_js/cli-help.js.map +1 -0
  39. memos/_js/cli.d.ts +28 -0
  40. memos/_js/cli.d.ts.map +1 -0
  41. memos/_js/cli.js +2880 -0
  42. memos/_js/cli.js.map +1 -0
  43. memos/_js/compression.d.ts +25 -0
  44. memos/_js/compression.d.ts.map +1 -0
  45. memos/_js/compression.js +319 -0
  46. memos/_js/compression.js.map +1 -0
  47. memos/_js/confidence-machine.d.ts +99 -0
  48. memos/_js/confidence-machine.d.ts.map +1 -0
  49. memos/_js/confidence-machine.js +222 -0
  50. memos/_js/confidence-machine.js.map +1 -0
  51. memos/_js/context-pack.d.ts +506 -0
  52. memos/_js/context-pack.d.ts.map +1 -0
  53. memos/_js/context-pack.js +1435 -0
  54. memos/_js/context-pack.js.map +1 -0
  55. memos/_js/contradictions.d.ts +160 -0
  56. memos/_js/contradictions.d.ts.map +1 -0
  57. memos/_js/contradictions.js +255 -0
  58. memos/_js/contradictions.js.map +1 -0
  59. memos/_js/dashboard.d.ts +30 -0
  60. memos/_js/dashboard.d.ts.map +1 -0
  61. memos/_js/dashboard.js +302 -0
  62. memos/_js/dashboard.js.map +1 -0
  63. memos/_js/ebbinghaus.d.ts +29 -0
  64. memos/_js/ebbinghaus.d.ts.map +1 -0
  65. memos/_js/ebbinghaus.js +32 -0
  66. memos/_js/ebbinghaus.js.map +1 -0
  67. memos/_js/embedding-queue.d.ts +190 -0
  68. memos/_js/embedding-queue.d.ts.map +1 -0
  69. memos/_js/embedding-queue.js +316 -0
  70. memos/_js/embedding-queue.js.map +1 -0
  71. memos/_js/embeddings.d.ts +187 -0
  72. memos/_js/embeddings.d.ts.map +1 -0
  73. memos/_js/embeddings.js +633 -0
  74. memos/_js/embeddings.js.map +1 -0
  75. memos/_js/entity-extraction.d.ts +54 -0
  76. memos/_js/entity-extraction.d.ts.map +1 -0
  77. memos/_js/entity-extraction.js +325 -0
  78. memos/_js/entity-extraction.js.map +1 -0
  79. memos/_js/entity-index.d.ts +93 -0
  80. memos/_js/entity-index.d.ts.map +1 -0
  81. memos/_js/entity-index.js +165 -0
  82. memos/_js/entity-index.js.map +1 -0
  83. memos/_js/event-memory.d.ts +147 -0
  84. memos/_js/event-memory.d.ts.map +1 -0
  85. memos/_js/event-memory.js +290 -0
  86. memos/_js/event-memory.js.map +1 -0
  87. memos/_js/external-import.d.ts +98 -0
  88. memos/_js/external-import.d.ts.map +1 -0
  89. memos/_js/external-import.js +669 -0
  90. memos/_js/external-import.js.map +1 -0
  91. memos/_js/fidelity.d.ts +367 -0
  92. memos/_js/fidelity.d.ts.map +1 -0
  93. memos/_js/fidelity.js +911 -0
  94. memos/_js/fidelity.js.map +1 -0
  95. memos/_js/graph-expansion.d.ts +119 -0
  96. memos/_js/graph-expansion.d.ts.map +1 -0
  97. memos/_js/graph-expansion.js +213 -0
  98. memos/_js/graph-expansion.js.map +1 -0
  99. memos/_js/graph-mermaid.d.ts +13 -0
  100. memos/_js/graph-mermaid.d.ts.map +1 -0
  101. memos/_js/graph-mermaid.js +32 -0
  102. memos/_js/graph-mermaid.js.map +1 -0
  103. memos/_js/graph.d.ts +170 -0
  104. memos/_js/graph.d.ts.map +1 -0
  105. memos/_js/graph.js +482 -0
  106. memos/_js/graph.js.map +1 -0
  107. memos/_js/harness.d.ts +63 -0
  108. memos/_js/harness.d.ts.map +1 -0
  109. memos/_js/harness.js +93 -0
  110. memos/_js/harness.js.map +1 -0
  111. memos/_js/importance.d.ts +118 -0
  112. memos/_js/importance.d.ts.map +1 -0
  113. memos/_js/importance.js +144 -0
  114. memos/_js/importance.js.map +1 -0
  115. memos/_js/index.d.ts +51 -0
  116. memos/_js/index.d.ts.map +1 -0
  117. memos/_js/index.js +53 -0
  118. memos/_js/index.js.map +1 -0
  119. memos/_js/mcp.d.ts +58 -0
  120. memos/_js/mcp.d.ts.map +1 -0
  121. memos/_js/mcp.js +1853 -0
  122. memos/_js/mcp.js.map +1 -0
  123. memos/_js/memory.d.ts +1762 -0
  124. memos/_js/memory.d.ts.map +1 -0
  125. memos/_js/memory.js +6283 -0
  126. memos/_js/memory.js.map +1 -0
  127. memos/_js/procedural.d.ts +74 -0
  128. memos/_js/procedural.d.ts.map +1 -0
  129. memos/_js/procedural.js +125 -0
  130. memos/_js/procedural.js.map +1 -0
  131. memos/_js/project-config.d.ts +68 -0
  132. memos/_js/project-config.d.ts.map +1 -0
  133. memos/_js/project-config.js +59 -0
  134. memos/_js/project-config.js.map +1 -0
  135. memos/_js/proof-of-forgetting.d.ts +57 -0
  136. memos/_js/proof-of-forgetting.d.ts.map +1 -0
  137. memos/_js/proof-of-forgetting.js +80 -0
  138. memos/_js/proof-of-forgetting.js.map +1 -0
  139. memos/_js/provenance.d.ts +122 -0
  140. memos/_js/provenance.d.ts.map +1 -0
  141. memos/_js/provenance.js +182 -0
  142. memos/_js/provenance.js.map +1 -0
  143. memos/_js/quarantine-commands.d.ts +23 -0
  144. memos/_js/quarantine-commands.d.ts.map +1 -0
  145. memos/_js/quarantine-commands.js +42 -0
  146. memos/_js/quarantine-commands.js.map +1 -0
  147. memos/_js/quarantine.d.ts +120 -0
  148. memos/_js/quarantine.d.ts.map +1 -0
  149. memos/_js/quarantine.js +873 -0
  150. memos/_js/quarantine.js.map +1 -0
  151. memos/_js/redact.d.ts +12 -0
  152. memos/_js/redact.d.ts.map +1 -0
  153. memos/_js/redact.js +26 -0
  154. memos/_js/redact.js.map +1 -0
  155. memos/_js/repl.d.ts +57 -0
  156. memos/_js/repl.d.ts.map +1 -0
  157. memos/_js/repl.js +295 -0
  158. memos/_js/repl.js.map +1 -0
  159. memos/_js/rerank-local.d.ts +61 -0
  160. memos/_js/rerank-local.d.ts.map +1 -0
  161. memos/_js/rerank-local.js +210 -0
  162. memos/_js/rerank-local.js.map +1 -0
  163. memos/_js/retain-filter.d.ts +98 -0
  164. memos/_js/retain-filter.d.ts.map +1 -0
  165. memos/_js/retain-filter.js +232 -0
  166. memos/_js/retain-filter.js.map +1 -0
  167. memos/_js/retrieval.d.ts +265 -0
  168. memos/_js/retrieval.d.ts.map +1 -0
  169. memos/_js/retrieval.js +1018 -0
  170. memos/_js/retrieval.js.map +1 -0
  171. memos/_js/revert.d.ts +77 -0
  172. memos/_js/revert.d.ts.map +1 -0
  173. memos/_js/revert.js +252 -0
  174. memos/_js/revert.js.map +1 -0
  175. memos/_js/scope.d.ts +30 -0
  176. memos/_js/scope.d.ts.map +1 -0
  177. memos/_js/scope.js +71 -0
  178. memos/_js/scope.js.map +1 -0
  179. memos/_js/semantic-screen.d.ts +152 -0
  180. memos/_js/semantic-screen.d.ts.map +1 -0
  181. memos/_js/semantic-screen.js +290 -0
  182. memos/_js/semantic-screen.js.map +1 -0
  183. memos/_js/session-hooks.d.ts +127 -0
  184. memos/_js/session-hooks.d.ts.map +1 -0
  185. memos/_js/session-hooks.js +482 -0
  186. memos/_js/session-hooks.js.map +1 -0
  187. memos/_js/storage/sqlite.d.ts +427 -0
  188. memos/_js/storage/sqlite.d.ts.map +1 -0
  189. memos/_js/storage/sqlite.js +2428 -0
  190. memos/_js/storage/sqlite.js.map +1 -0
  191. memos/_js/sync.d.ts +213 -0
  192. memos/_js/sync.d.ts.map +1 -0
  193. memos/_js/sync.js +389 -0
  194. memos/_js/sync.js.map +1 -0
  195. memos/_js/tamper-log.d.ts +98 -0
  196. memos/_js/tamper-log.d.ts.map +1 -0
  197. memos/_js/tamper-log.js +327 -0
  198. memos/_js/tamper-log.js.map +1 -0
  199. memos/_js/temporal.d.ts +92 -0
  200. memos/_js/temporal.d.ts.map +1 -0
  201. memos/_js/temporal.js +536 -0
  202. memos/_js/temporal.js.map +1 -0
  203. memos/_js/time-travel.d.ts +38 -0
  204. memos/_js/time-travel.d.ts.map +1 -0
  205. memos/_js/time-travel.js +46 -0
  206. memos/_js/time-travel.js.map +1 -0
  207. memos/_js/timestamp-format.d.ts +18 -0
  208. memos/_js/timestamp-format.d.ts.map +1 -0
  209. memos/_js/timestamp-format.js +52 -0
  210. memos/_js/timestamp-format.js.map +1 -0
  211. memos/_js/triple-extraction.d.ts +31 -0
  212. memos/_js/triple-extraction.d.ts.map +1 -0
  213. memos/_js/triple-extraction.js +116 -0
  214. memos/_js/triple-extraction.js.map +1 -0
  215. memos/_js/types.d.ts +2474 -0
  216. memos/_js/types.d.ts.map +1 -0
  217. memos/_js/types.js +20 -0
  218. memos/_js/types.js.map +1 -0
  219. memos/_js/updater.d.ts +95 -0
  220. memos/_js/updater.d.ts.map +1 -0
  221. memos/_js/updater.js +268 -0
  222. memos/_js/updater.js.map +1 -0
  223. memos/_js/version.d.ts +17 -0
  224. memos/_js/version.d.ts.map +1 -0
  225. memos/_js/version.js +34 -0
  226. memos/_js/version.js.map +1 -0
  227. memos/_js/vision/clip-query-cache.d.ts +58 -0
  228. memos/_js/vision/clip-query-cache.d.ts.map +1 -0
  229. memos/_js/vision/clip-query-cache.js +79 -0
  230. memos/_js/vision/clip-query-cache.js.map +1 -0
  231. memos/_js/vision/clip.d.ts +113 -0
  232. memos/_js/vision/clip.d.ts.map +1 -0
  233. memos/_js/vision/clip.js +224 -0
  234. memos/_js/vision/clip.js.map +1 -0
  235. memos/_js/vision/fake-clip.d.ts +55 -0
  236. memos/_js/vision/fake-clip.d.ts.map +1 -0
  237. memos/_js/vision/fake-clip.js +122 -0
  238. memos/_js/vision/fake-clip.js.map +1 -0
  239. memos/_js/vision/index.d.ts +15 -0
  240. memos/_js/vision/index.d.ts.map +1 -0
  241. memos/_js/vision/index.js +13 -0
  242. memos/_js/vision/index.js.map +1 -0
  243. memos/_js/vision/store.d.ts +137 -0
  244. memos/_js/vision/store.d.ts.map +1 -0
  245. memos/_js/vision/store.js +383 -0
  246. memos/_js/vision/store.js.map +1 -0
  247. memos/adapters/__init__.py +0 -0
  248. memos/adapters/crewai.py +385 -0
  249. memos/adapters/langchain.py +301 -0
  250. memos/adapters/ollama.py +396 -0
  251. memos/server/__init__.py +0 -0
  252. memos/server/main.py +547 -0
  253. memos/server/routes.py +360 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Bounded LRU cache for CLIP query vectors.
3
+ *
4
+ * CLIP text-encoding is cheap (~10–30 ms on CPU for a 20-token query)
5
+ * but not free, and conversations repeat queries (the same query fans
6
+ * out across multiple retrieval legs). This mirrors the text path's
7
+ * `queryEmbeddingCache` LRU, but it is a SEPARATE cache by design:
8
+ *
9
+ * - `embedQueryCached` is keyed `provider.id:model:query` for the TEXT
10
+ * provider. CLIP text encoding is a different model in a different
11
+ * vector space and must not share that LRU — a shared LRU would let a
12
+ * CLIP query vector be served to a text-semantic query (or vice
13
+ * versa), which is the same silent-corruption class as the
14
+ * vectorCache namespace bug.
15
+ * - The key here is `clip:<model>:<query>`, namespaced away from every
16
+ * text cache key.
17
+ *
18
+ * The cache is bounded (default 512 entries ≈ 512 × 512 floats ≈ 1 MB)
19
+ * with FIFO eviction on insert and LRU touch on hit, matching the text
20
+ * cache's semantics.
21
+ *
22
+ * @module @memos/vision/clip-query-cache
23
+ */
24
+ /** Default bound on cached CLIP query vectors. */
25
+ export const DEFAULT_CLIP_QUERY_CACHE_MAX = 512;
26
+ /**
27
+ * A bounded LRU cache for CLIP query vectors.
28
+ *
29
+ * Usage:
30
+ * ```ts
31
+ * const cache = new ClipQueryCache();
32
+ * const v = await cache.get("a red error banner", clipProvider);
33
+ * ```
34
+ */
35
+ export class ClipQueryCache {
36
+ entries = new Map();
37
+ max;
38
+ constructor(max = DEFAULT_CLIP_QUERY_CACHE_MAX) {
39
+ this.max = Math.max(1, max);
40
+ }
41
+ /** Number of cached vectors. */
42
+ get size() {
43
+ return this.entries.size;
44
+ }
45
+ /** Drop every cached vector. */
46
+ clear() {
47
+ this.entries.clear();
48
+ }
49
+ /**
50
+ * Encode `query` with `provider.embedImageText`, memoized under
51
+ * `clip:<model>:<query>`.
52
+ *
53
+ * Returns null when the provider cannot encode text (no
54
+ * `embedImageText`) — the caller degrades to the caption leg. A
55
+ * provider that THROWS propagates the error: caching a failure would
56
+ * pin it, and a failed encode is a real signal the caller must see.
57
+ */
58
+ async get(query, provider) {
59
+ if (!provider.embedImageText)
60
+ return null;
61
+ const key = `clip:${provider.model}:${query}`;
62
+ const hit = this.entries.get(key);
63
+ if (hit) {
64
+ // LRU touch: re-insert to mark most-recently-used.
65
+ this.entries.delete(key);
66
+ this.entries.set(key, hit);
67
+ return hit;
68
+ }
69
+ const vector = await provider.embedImageText(query);
70
+ this.entries.set(key, vector);
71
+ if (this.entries.size > this.max) {
72
+ const oldest = this.entries.keys().next();
73
+ if (!oldest.done)
74
+ this.entries.delete(oldest.value);
75
+ }
76
+ return vector;
77
+ }
78
+ }
79
+ //# sourceMappingURL=clip-query-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip-query-cache.js","sourceRoot":"","sources":["../../src/vision/clip-query-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,kDAAkD;AAClD,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,OAAO,cAAc;IACR,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC7C,GAAG,CAAS;IAE7B,YAAY,GAAG,GAAG,4BAA4B;QAC5C,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,gCAAgC;IAChC,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,gCAAgC;IAChC,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAG,CACP,KAAa,EACb,QAGC;QAED,IAAI,CAAC,QAAQ,CAAC,cAAc;YAAE,OAAO,IAAI,CAAC;QAC1C,MAAM,GAAG,GAAG,QAAQ,QAAQ,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,GAAG,EAAE,CAAC;YACR,mDAAmD;YACnD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3B,OAAO,GAAG,CAAC;QACb,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,IAAI;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * CLIP image-embedding provider (Track B of image memory).
3
+ *
4
+ * Encodes images with `Xenova/clip-vit-base-patch32` via
5
+ * `@huggingface/transformers` (the same optional peer dependency the text
6
+ * FastEmbed provider uses), entirely in-process — no API key, no network
7
+ * beyond the first model download (~350 MB, cached on disk like the text
8
+ * model).
9
+ *
10
+ * Design constraints, all deliberate:
11
+ *
12
+ * 1. **Lazy load.** The pipeline is constructed on the FIRST embed call,
13
+ * not in the constructor — constructing a MemOS instance must never
14
+ * pay a model download. Mirrors `FastEmbedEmbeddingProvider.resolve()`.
15
+ * 2. **Fail soft.** If the transformers package is missing (or the model
16
+ * can't be built), `embedImage` throws a typed {@link ClipUnavailableError}
17
+ * so the write path can record `clipStatus: "failed"` — but the
18
+ * provider NEVER silently substitutes a text vector for an image
19
+ * vector. That substitution is the one failure mode that corrupts
20
+ * retrieval invisibly.
21
+ * 3. **Separate contract from EmbeddingProvider.** CLIP's 512-d space is
22
+ * not comparable with bge's 384-d text space; the two vector tables
23
+ * exist precisely so they can never be mixed. The class satisfies
24
+ * {@link EmbedImageProvider} so it can also be dropped into
25
+ * `vision.clip.embedImage` directly.
26
+ * 4. **Text side.** `embedImageText` encodes a query through CLIP's text
27
+ * encoder. Note CLIP's documented limitation: its text encoder is
28
+ * trained on short captions (<77 tokens) and is a weak text retriever
29
+ * — it is excellent at cross-modal matching ("a cat on a couch" →
30
+ * that photo), which is exactly what the image leg uses it for.
31
+ *
32
+ * @module @memos/vision/clip
33
+ */
34
+ import type { EmbedImageProvider, EmbeddingVector } from "../types.js";
35
+ /** Default CLIP model — ViT-B/32, 512-d image space. */
36
+ export declare const DEFAULT_CLIP_MODEL = "Xenova/clip-vit-base-patch32";
37
+ /** Default CLIP dimensionality. */
38
+ export declare const DEFAULT_CLIP_DIMENSIONS = 512;
39
+ /**
40
+ * Thrown when the CLIP pipeline could not be constructed (missing
41
+ * optional dependency, model load failure, or no callable interface).
42
+ * The write path catches this and records `clipStatus: "failed"`;
43
+ * nothing ever falls back to a text vector.
44
+ */
45
+ export declare class ClipUnavailableError extends Error {
46
+ readonly cause?: unknown;
47
+ constructor(message: string, cause?: unknown);
48
+ }
49
+ export interface ClipEmbeddingProviderOptions {
50
+ model?: string;
51
+ dimensions?: number;
52
+ }
53
+ /**
54
+ * CLIP image encoder. Lazy, optional-dependency-tolerant, and separate
55
+ * from the text embedding pipeline by construction.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const clip = new ClipEmbeddingProvider();
60
+ * const vector = await clip.embedImage(fs.readFileSync("cat.png")); // 512-d
61
+ * const query = await clip.embedImageText("a cat on a couch"); // 512-d
62
+ * ```
63
+ */
64
+ export declare class ClipEmbeddingProvider implements EmbedImageProvider {
65
+ readonly id = "clip";
66
+ readonly model: string;
67
+ readonly dimensions: number;
68
+ private extractor;
69
+ private resolveAttempted;
70
+ /** Why the pipeline could not be built, or null when it loaded fine. */
71
+ private failureReason;
72
+ constructor(options?: ClipEmbeddingProviderOptions);
73
+ /**
74
+ * Why the CLIP pipeline is unavailable, or null when it loaded (or has
75
+ * not been probed yet). The write path records this in
76
+ * `metadata.clip.error` so a degraded install is diagnosable.
77
+ */
78
+ get fallbackReason(): string | null;
79
+ /** True once the pipeline has been successfully constructed. */
80
+ get available(): boolean;
81
+ /**
82
+ * Lazily resolve the transformers pipeline. Tries
83
+ * `@huggingface/transformers` first, then the legacy
84
+ * `@xenova/transformers`, exactly like the text provider's fallback
85
+ * chain. Resolved at most once — a failure is sticky so repeated
86
+ * embed calls fail fast instead of retrying a doomed model load.
87
+ */
88
+ private resolve;
89
+ /** Reference to the lazily-resolved `RawImage`, or null. */
90
+ private resolveRawImage;
91
+ /**
92
+ * Encode image bytes (or a path/URL) into the CLIP image space.
93
+ *
94
+ * A `Buffer` is wrapped in a `Blob` because `RawImage.read` accepts
95
+ * `RawImage | string | URL | Blob | canvas` natively — a raw Node
96
+ * Buffer is not one of those.
97
+ *
98
+ * @throws {@link ClipUnavailableError} when the pipeline is missing.
99
+ */
100
+ embedImage(image: Buffer | string): Promise<EmbeddingVector>;
101
+ /**
102
+ * Encode a text query into the CLIP image space, so a text→image
103
+ * cosine query works. Uses the `feature-extraction` pipeline (CLIP's
104
+ * text encoder).
105
+ *
106
+ * When unavailable, the caller should degrade to the caption leg
107
+ * (Track A) rather than fail the query.
108
+ *
109
+ * @throws {@link ClipUnavailableError} when the text pipeline is missing.
110
+ */
111
+ embedImageText(query: string): Promise<EmbeddingVector>;
112
+ }
113
+ //# sourceMappingURL=clip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../src/vision/clip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEvE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,iCAAiC,CAAC;AAEjE,mCAAmC;AACnC,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAG3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IAF1B,YACE,OAAO,EAAE,MAAM,EACN,KAAK,CAAC,EAAE,OAAO,EAIzB;CACF;AAoBD,MAAM,WAAW,4BAA4B;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,YAAW,kBAAkB;IAC9D,QAAQ,CAAC,EAAE,UAAU;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,gBAAgB,CAAS;IACjC,wEAAwE;IACxE,OAAO,CAAC,aAAa,CAAuB;IAE5C,YAAY,OAAO,GAAE,4BAAiC,EAGrD;IAED;;;;OAIG;IACH,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAElC;IAED,gEAAgE;IAChE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;;;OAMG;YACW,OAAO;IA2DrB,4DAA4D;YAC9C,eAAe;IAa7B;;;;;;;;OAQG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA2DjE;IAED;;;;;;;;;OASG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA6B5D;CACF"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * CLIP image-embedding provider (Track B of image memory).
3
+ *
4
+ * Encodes images with `Xenova/clip-vit-base-patch32` via
5
+ * `@huggingface/transformers` (the same optional peer dependency the text
6
+ * FastEmbed provider uses), entirely in-process — no API key, no network
7
+ * beyond the first model download (~350 MB, cached on disk like the text
8
+ * model).
9
+ *
10
+ * Design constraints, all deliberate:
11
+ *
12
+ * 1. **Lazy load.** The pipeline is constructed on the FIRST embed call,
13
+ * not in the constructor — constructing a MemOS instance must never
14
+ * pay a model download. Mirrors `FastEmbedEmbeddingProvider.resolve()`.
15
+ * 2. **Fail soft.** If the transformers package is missing (or the model
16
+ * can't be built), `embedImage` throws a typed {@link ClipUnavailableError}
17
+ * so the write path can record `clipStatus: "failed"` — but the
18
+ * provider NEVER silently substitutes a text vector for an image
19
+ * vector. That substitution is the one failure mode that corrupts
20
+ * retrieval invisibly.
21
+ * 3. **Separate contract from EmbeddingProvider.** CLIP's 512-d space is
22
+ * not comparable with bge's 384-d text space; the two vector tables
23
+ * exist precisely so they can never be mixed. The class satisfies
24
+ * {@link EmbedImageProvider} so it can also be dropped into
25
+ * `vision.clip.embedImage` directly.
26
+ * 4. **Text side.** `embedImageText` encodes a query through CLIP's text
27
+ * encoder. Note CLIP's documented limitation: its text encoder is
28
+ * trained on short captions (<77 tokens) and is a weak text retriever
29
+ * — it is excellent at cross-modal matching ("a cat on a couch" →
30
+ * that photo), which is exactly what the image leg uses it for.
31
+ *
32
+ * @module @memos/vision/clip
33
+ */
34
+ import { normalizeVector } from "../embeddings.js";
35
+ /** Default CLIP model — ViT-B/32, 512-d image space. */
36
+ export const DEFAULT_CLIP_MODEL = "Xenova/clip-vit-base-patch32";
37
+ /** Default CLIP dimensionality. */
38
+ export const DEFAULT_CLIP_DIMENSIONS = 512;
39
+ /**
40
+ * Thrown when the CLIP pipeline could not be constructed (missing
41
+ * optional dependency, model load failure, or no callable interface).
42
+ * The write path catches this and records `clipStatus: "failed"`;
43
+ * nothing ever falls back to a text vector.
44
+ */
45
+ export class ClipUnavailableError extends Error {
46
+ cause;
47
+ constructor(message, cause) {
48
+ super(message);
49
+ this.cause = cause;
50
+ this.name = "ClipUnavailableError";
51
+ }
52
+ }
53
+ /**
54
+ * CLIP image encoder. Lazy, optional-dependency-tolerant, and separate
55
+ * from the text embedding pipeline by construction.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const clip = new ClipEmbeddingProvider();
60
+ * const vector = await clip.embedImage(fs.readFileSync("cat.png")); // 512-d
61
+ * const query = await clip.embedImageText("a cat on a couch"); // 512-d
62
+ * ```
63
+ */
64
+ export class ClipEmbeddingProvider {
65
+ id = "clip";
66
+ model;
67
+ dimensions;
68
+ extractor = null;
69
+ resolveAttempted = false;
70
+ /** Why the pipeline could not be built, or null when it loaded fine. */
71
+ failureReason = null;
72
+ constructor(options = {}) {
73
+ this.model = options.model ?? DEFAULT_CLIP_MODEL;
74
+ this.dimensions = options.dimensions ?? DEFAULT_CLIP_DIMENSIONS;
75
+ }
76
+ /**
77
+ * Why the CLIP pipeline is unavailable, or null when it loaded (or has
78
+ * not been probed yet). The write path records this in
79
+ * `metadata.clip.error` so a degraded install is diagnosable.
80
+ */
81
+ get fallbackReason() {
82
+ return this.failureReason;
83
+ }
84
+ /** True once the pipeline has been successfully constructed. */
85
+ get available() {
86
+ return this.extractor !== null;
87
+ }
88
+ /**
89
+ * Lazily resolve the transformers pipeline. Tries
90
+ * `@huggingface/transformers` first, then the legacy
91
+ * `@xenova/transformers`, exactly like the text provider's fallback
92
+ * chain. Resolved at most once — a failure is sticky so repeated
93
+ * embed calls fail fast instead of retrying a doomed model load.
94
+ */
95
+ async resolve() {
96
+ if (this.resolveAttempted)
97
+ return;
98
+ this.resolveAttempted = true;
99
+ try {
100
+ const mod = (await import("@huggingface/transformers").catch(() => null));
101
+ const legacyMod = mod
102
+ ? null
103
+ : (await import("@xenova/transformers").catch(() => null));
104
+ const resolved = mod ?? legacyMod;
105
+ if (!resolved || typeof resolved.pipeline !== "function") {
106
+ this.failureReason =
107
+ "neither @huggingface/transformers nor @xenova/transformers is installed";
108
+ return;
109
+ }
110
+ const pipeline = await resolved.pipeline("image-feature-extraction", this.model);
111
+ // transformers.js v3+/v4 pipelines are Callable instances (the
112
+ // class is wrapped in a function closure); older shapes exposed
113
+ // `._call`. Normalize both behind one callable.
114
+ const callable = typeof pipeline === "function"
115
+ ? pipeline
116
+ : (pipeline._call ??
117
+ pipeline.feature ??
118
+ null);
119
+ if (!callable) {
120
+ this.failureReason = `pipeline for model "${this.model}" exposed no callable image-feature-extraction interface`;
121
+ return;
122
+ }
123
+ this.extractor = callable;
124
+ }
125
+ catch (err) {
126
+ this.failureReason = `CLIP pipeline construction failed for model "${this.model}": ${String(err).substring(0, 200)}`;
127
+ }
128
+ }
129
+ /** Reference to the lazily-resolved `RawImage`, or null. */
130
+ async resolveRawImage() {
131
+ const mod = (await import("@huggingface/transformers").catch(() => null));
132
+ if (mod?.RawImage)
133
+ return mod.RawImage;
134
+ const legacy = (await import("@xenova/transformers").catch(() => null));
135
+ return legacy?.RawImage ?? null;
136
+ }
137
+ /**
138
+ * Encode image bytes (or a path/URL) into the CLIP image space.
139
+ *
140
+ * A `Buffer` is wrapped in a `Blob` because `RawImage.read` accepts
141
+ * `RawImage | string | URL | Blob | canvas` natively — a raw Node
142
+ * Buffer is not one of those.
143
+ *
144
+ * @throws {@link ClipUnavailableError} when the pipeline is missing.
145
+ */
146
+ async embedImage(image) {
147
+ await this.resolve();
148
+ if (!this.extractor) {
149
+ throw new ClipUnavailableError(`CLIP image encoding unavailable: ${this.failureReason ?? "pipeline not resolved"}`);
150
+ }
151
+ let decoded = image;
152
+ if (typeof image !== "string") {
153
+ // Node Buffer → Blob. No mime known at this layer; the decoder
154
+ // sniffs the format from the header, same as a file path would be.
155
+ const blob = new Blob([new Uint8Array(image)]);
156
+ const RawImage = await this.resolveRawImage();
157
+ if (RawImage) {
158
+ try {
159
+ decoded = await RawImage.read(blob);
160
+ }
161
+ catch (err) {
162
+ throw new ClipUnavailableError(`CLIP image decode failed for model "${this.model}": ${String(err).substring(0, 200)}`, err);
163
+ }
164
+ }
165
+ else {
166
+ decoded = blob;
167
+ }
168
+ }
169
+ let out;
170
+ try {
171
+ out = await this.extractor(decoded);
172
+ }
173
+ catch (err) {
174
+ // An encode failure (unsupported/corrupt image bytes, ONNX
175
+ // runtime error) is a REAL failure the write path must see —
176
+ // wrap it so callers catch one typed error, never a bare Error
177
+ // they might mistake for something else. The provider still
178
+ // never substitutes a text vector.
179
+ throw new ClipUnavailableError(`CLIP image encoding failed for model "${this.model}": ${String(err).substring(0, 200)}`, err);
180
+ }
181
+ if (!out || !out.data || out.data.length === 0) {
182
+ throw new ClipUnavailableError(`CLIP image encoding produced an empty tensor for model "${this.model}"`);
183
+ }
184
+ if (out.data.length !== this.dimensions) {
185
+ // A projection mismatch means the configured dimensions no longer
186
+ // describe the model's output space — refuse rather than persist
187
+ // vectors that querySimilarImages would filter out or, worse,
188
+ // score against the wrong space.
189
+ throw new ClipUnavailableError(`CLIP model "${this.model}" produced ${out.data.length} dims, expected ${this.dimensions}`);
190
+ }
191
+ return normalizeVector(Array.from(out.data));
192
+ }
193
+ /**
194
+ * Encode a text query into the CLIP image space, so a text→image
195
+ * cosine query works. Uses the `feature-extraction` pipeline (CLIP's
196
+ * text encoder).
197
+ *
198
+ * When unavailable, the caller should degrade to the caption leg
199
+ * (Track A) rather than fail the query.
200
+ *
201
+ * @throws {@link ClipUnavailableError} when the text pipeline is missing.
202
+ */
203
+ async embedImageText(query) {
204
+ await this.resolve();
205
+ if (!this.extractor) {
206
+ throw new ClipUnavailableError(`CLIP text encoding unavailable: ${this.failureReason ?? "pipeline not resolved"}`);
207
+ }
208
+ let out;
209
+ try {
210
+ out = await this.extractor(query);
211
+ }
212
+ catch (err) {
213
+ throw new ClipUnavailableError(`CLIP text encoding failed for model "${this.model}": ${String(err).substring(0, 200)}`, err);
214
+ }
215
+ if (!out || !out.data || out.data.length === 0) {
216
+ throw new ClipUnavailableError(`CLIP text encoding produced an empty tensor for model "${this.model}"`);
217
+ }
218
+ if (out.data.length !== this.dimensions) {
219
+ throw new ClipUnavailableError(`CLIP text model "${this.model}" produced ${out.data.length} dims, expected ${this.dimensions}`);
220
+ }
221
+ return normalizeVector(Array.from(out.data));
222
+ }
223
+ }
224
+ //# sourceMappingURL=clip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip.js","sourceRoot":"","sources":["../../src/vision/clip.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Fake CLIP provider — the offline test seam for the image-embedding
3
+ * foundation.
4
+ *
5
+ * Produces a DETERMINISTIC 512-d normalized vector from the pixel bytes
6
+ * (a feature hash over the buffer), with no model download, no network,
7
+ * and no `@huggingface/transformers` dependency. It exists so the rest
8
+ * of the image-memory suite (storage isolation, querySimilarImages,
9
+ * the vectorCache namespace guarantee) can run offline and
10
+ * deterministically — exactly the role the FastEmbed local-hash
11
+ * fallback plays for text.
12
+ *
13
+ * It is deliberately NOT wired into production code: it is exported for
14
+ * tests and for callers who want a deterministic stand-in. Real installs
15
+ * use {@link ClipEmbeddingProvider} via `vision.clip.embedImage` or the
16
+ * lazy default.
17
+ *
18
+ * Semantics:
19
+ * - `embedImage(bytes)` hashes the raw bytes → 512-d, normalized.
20
+ * - `embedImageText(query)` hashes the query STRING → 512-d, normalized.
21
+ * - Same input always gives the same vector (assert-able in tests).
22
+ * - `embedImageText` with a query whose text matches a previously
23
+ * embedded image's content produces a related-but-distinct vector —
24
+ * the fake is a hash, not a similarity model, so cross-modal
25
+ * "similarity" is only what tests choose to construct.
26
+ *
27
+ * @module @memos/vision/fake-clip
28
+ */
29
+ import type { EmbedImageProvider, EmbeddingVector } from "../types.js";
30
+ export interface FakeClipProviderOptions {
31
+ model?: string;
32
+ dimensions?: number;
33
+ }
34
+ /**
35
+ * Deterministic offline stand-in for {@link ClipEmbeddingProvider}.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const clip = new FakeClipProvider();
40
+ * const v = await clip.embedImage(Buffer.from([0xff, 0xd8, 0xff])); // 512-d
41
+ * ```
42
+ */
43
+ export declare class FakeClipProvider implements EmbedImageProvider {
44
+ readonly id = "fake-clip";
45
+ readonly model: string;
46
+ readonly dimensions: number;
47
+ /** Counts embedImage calls — useful for asserting cache behavior. */
48
+ imageCalls: number;
49
+ /** Counts embedImageText calls. */
50
+ textCalls: number;
51
+ constructor(options?: FakeClipProviderOptions);
52
+ embedImage(image: Buffer | string): Promise<EmbeddingVector>;
53
+ embedImageText(query: string): Promise<EmbeddingVector>;
54
+ }
55
+ //# sourceMappingURL=fake-clip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-clip.d.ts","sourceRoot":"","sources":["../../src/vision/fake-clip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA4DvE,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,YAAW,kBAAkB;IACzD,QAAQ,CAAC,EAAE,eAAe;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,qEAAqE;IACrE,UAAU,SAAK;IACf,mCAAmC;IACnC,SAAS,SAAK;IAEd,YAAY,OAAO,GAAE,uBAA4B,EAGhD;IAEK,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAOjE;IAEK,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAO5D;CACF"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Fake CLIP provider — the offline test seam for the image-embedding
3
+ * foundation.
4
+ *
5
+ * Produces a DETERMINISTIC 512-d normalized vector from the pixel bytes
6
+ * (a feature hash over the buffer), with no model download, no network,
7
+ * and no `@huggingface/transformers` dependency. It exists so the rest
8
+ * of the image-memory suite (storage isolation, querySimilarImages,
9
+ * the vectorCache namespace guarantee) can run offline and
10
+ * deterministically — exactly the role the FastEmbed local-hash
11
+ * fallback plays for text.
12
+ *
13
+ * It is deliberately NOT wired into production code: it is exported for
14
+ * tests and for callers who want a deterministic stand-in. Real installs
15
+ * use {@link ClipEmbeddingProvider} via `vision.clip.embedImage` or the
16
+ * lazy default.
17
+ *
18
+ * Semantics:
19
+ * - `embedImage(bytes)` hashes the raw bytes → 512-d, normalized.
20
+ * - `embedImageText(query)` hashes the query STRING → 512-d, normalized.
21
+ * - Same input always gives the same vector (assert-able in tests).
22
+ * - `embedImageText` with a query whose text matches a previously
23
+ * embedded image's content produces a related-but-distinct vector —
24
+ * the fake is a hash, not a similarity model, so cross-modal
25
+ * "similarity" is only what tests choose to construct.
26
+ *
27
+ * @module @memos/vision/fake-clip
28
+ */
29
+ import { normalizeVector } from "../embeddings.js";
30
+ /** Feature-hash weight table shared by image and text sides. */
31
+ const FEATURE_WEIGHT = 1;
32
+ const NGRAM_WEIGHT = 0.12;
33
+ const BIGRAM_WEIGHT = 0.35;
34
+ /**
35
+ * Deterministic feature-hash vector, mirroring the text local-hash
36
+ * fallback's construction so the two spaces behave the same way under
37
+ * test (same norm, same shape, same determinism).
38
+ */
39
+ function hashVector(source, dims) {
40
+ const vector = new Array(dims).fill(0);
41
+ const add = (feature, weight) => {
42
+ // FNV-1a 32-bit over the feature string, folded into the dims range.
43
+ let hash = 0x811c9dc5;
44
+ for (let i = 0; i < feature.length; i += 1) {
45
+ hash ^= feature.charCodeAt(i);
46
+ hash = Math.imul(hash, 0x01000193) >>> 0;
47
+ }
48
+ const slot = hash % dims;
49
+ const sign = (hash >>> 16) % 2 === 0 ? 1 : -1;
50
+ vector[slot] += sign * weight;
51
+ };
52
+ // Byte-level bigrams over the raw buffer: stable, cheap, and sensitive
53
+ // to both ordering and content (so two different images hash
54
+ // differently, and the same bytes always hash identically).
55
+ for (let i = 0; i + 1 < source.length; i += 2) {
56
+ add(`b${source[i]}:${source[i + 1]}`, FEATURE_WEIGHT);
57
+ }
58
+ // Structural n-grams of the first 4 bytes (header signature) — a
59
+ // coarse, real-ish signal that two JPEGs share more than two random
60
+ // PNGs do, without pretending to be a vision model.
61
+ if (source.length >= 4) {
62
+ add(`h${source[0]}:${source[1]}:${source[2]}:${source[3]}`, NGRAM_WEIGHT);
63
+ add(`h${source[0]}:${source[1]}`, BIGRAM_WEIGHT);
64
+ }
65
+ // Length signature: guards against short inputs hashing to the
66
+ // all-zero vector (an empty normalized vector scores 0 against
67
+ // everything and would make two distinct short inputs look equal).
68
+ add(`len:${source.length}`, FEATURE_WEIGHT);
69
+ if (source.length === 0) {
70
+ // A truly empty input still gets a deterministic non-zero vector so
71
+ // it never collides with another short input's zero vector.
72
+ vector[0] += FEATURE_WEIGHT;
73
+ }
74
+ return normalizeVector(vector);
75
+ }
76
+ /** Hash a string into the same feature space. */
77
+ function hashText(text, dims) {
78
+ const bytes = new Uint8Array(text.length * 2);
79
+ for (let i = 0; i < text.length; i += 1) {
80
+ bytes[i * 2] = text.charCodeAt(i) & 0xff;
81
+ bytes[i * 2 + 1] = (text.charCodeAt(i) >> 8) & 0xff;
82
+ }
83
+ return hashVector(bytes, dims);
84
+ }
85
+ /**
86
+ * Deterministic offline stand-in for {@link ClipEmbeddingProvider}.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const clip = new FakeClipProvider();
91
+ * const v = await clip.embedImage(Buffer.from([0xff, 0xd8, 0xff])); // 512-d
92
+ * ```
93
+ */
94
+ export class FakeClipProvider {
95
+ id = "fake-clip";
96
+ model;
97
+ dimensions;
98
+ /** Counts embedImage calls — useful for asserting cache behavior. */
99
+ imageCalls = 0;
100
+ /** Counts embedImageText calls. */
101
+ textCalls = 0;
102
+ constructor(options = {}) {
103
+ this.model = options.model ?? "fake-clip-vit-base-patch32";
104
+ this.dimensions = options.dimensions ?? 512;
105
+ }
106
+ async embedImage(image) {
107
+ this.imageCalls += 1;
108
+ const bytes = typeof image === "string"
109
+ ? new TextEncoder().encode(image)
110
+ : new Uint8Array(image);
111
+ return hashVector(bytes, this.dimensions);
112
+ }
113
+ async embedImageText(query) {
114
+ this.textCalls += 1;
115
+ // The model id is mixed into the hash so two fake providers with
116
+ // different model names (but identical query text) produce
117
+ // DIFFERENT vectors — that's what proves the cache is keyed by
118
+ // model, not by query alone.
119
+ return hashText(`${this.model}\u0000${query}`, this.dimensions);
120
+ }
121
+ }
122
+ //# sourceMappingURL=fake-clip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-clip.js","sourceRoot":"","sources":["../../src/vision/fake-clip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,gEAAgE;AAChE,MAAM,cAAc,GAAG,CAAC,CAAC;AACzB,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,aAAa,GAAG,IAAI,CAAC;AAE3B;;;;GAIG;AACH,SAAS,UAAU,CAAC,MAAkB,EAAE,IAAY;IAClD,MAAM,MAAM,GAAG,IAAI,KAAK,CAAS,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,CAAC,OAAe,EAAE,MAAc,EAAQ,EAAE;QACpD,qEAAqE;QACrE,IAAI,IAAI,GAAG,UAAU,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC9B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC;IAChC,CAAC,CAAC;IACF,uEAAuE;IACvE,6DAA6D;IAC7D,4DAA4D;IAC5D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;IACxD,CAAC;IACD,iEAAiE;IACjE,oEAAoE;IACpE,oDAAoD;IACpD,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACvB,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QAC1E,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IACnD,CAAC;IACD,+DAA+D;IAC/D,+DAA+D;IAC/D,mEAAmE;IACnE,GAAG,CAAC,OAAO,MAAM,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,oEAAoE;QACpE,4DAA4D;QAC5D,MAAM,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC;IAC9B,CAAC;IACD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,iDAAiD;AACjD,SAAS,QAAQ,CAAC,IAAY,EAAE,IAAY;IAC1C,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACzC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IACtD,CAAC;IACD,OAAO,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACjC,CAAC;AAOD;;;;;;;;GAQG;AACH,MAAM,OAAO,gBAAgB;IAClB,EAAE,GAAG,WAAW,CAAC;IACjB,KAAK,CAAS;IACd,UAAU,CAAS;IAE5B,qEAAqE;IACrE,UAAU,GAAG,CAAC,CAAC;IACf,mCAAmC;IACnC,SAAS,GAAG,CAAC,CAAC;IAEd,YAAY,OAAO,GAA4B,EAAE;QAC/C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,4BAA4B,CAAC;QAC3D,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,KAAsB;QACrC,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;QACrB,MAAM,KAAK,GACT,OAAO,KAAK,KAAK,QAAQ;YACvB,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;YACjC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,KAAa;QAChC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;QACpB,iEAAiE;QACjE,2DAA2D;QAC3D,+DAA+D;QAC/D,6BAA6B;QAC7B,OAAO,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,SAAS,KAAK,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAClE,CAAC;CACF"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Vision (image memory) module surface.
3
+ *
4
+ * Re-exports the CLIP provider, the offline fake provider, and the CLIP
5
+ * query cache so callers import from `@memos/vision` rather than
6
+ * reaching into individual files.
7
+ *
8
+ * @module @memos/vision
9
+ */
10
+ export { ClipEmbeddingProvider, ClipUnavailableError, DEFAULT_CLIP_MODEL, DEFAULT_CLIP_DIMENSIONS, } from "./clip.js";
11
+ export type { ClipEmbeddingProviderOptions } from "./clip.js";
12
+ export { FakeClipProvider } from "./fake-clip.js";
13
+ export type { FakeClipProviderOptions } from "./fake-clip.js";
14
+ export { ClipQueryCache, DEFAULT_CLIP_QUERY_CACHE_MAX, } from "./clip-query-cache.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vision/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,EACL,cAAc,EACd,4BAA4B,GAC7B,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Vision (image memory) module surface.
3
+ *
4
+ * Re-exports the CLIP provider, the offline fake provider, and the CLIP
5
+ * query cache so callers import from `@memos/vision` rather than
6
+ * reaching into individual files.
7
+ *
8
+ * @module @memos/vision
9
+ */
10
+ export { ClipEmbeddingProvider, ClipUnavailableError, DEFAULT_CLIP_MODEL, DEFAULT_CLIP_DIMENSIONS, } from "./clip.js";
11
+ export { FakeClipProvider } from "./fake-clip.js";
12
+ export { ClipQueryCache, DEFAULT_CLIP_QUERY_CACHE_MAX, } from "./clip-query-cache.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vision/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,EACL,cAAc,EACd,4BAA4B,GAC7B,MAAM,uBAAuB,CAAC"}