@tpsdev-ai/flair 0.3.1 → 0.3.4

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.
@@ -1,127 +1,51 @@
1
1
  /**
2
- * In-process embeddings via harper-fabric-embeddings (v0.2.1+).
2
+ * embeddings-provider.ts
3
3
  *
4
- * Uses process-level singleton for cross-thread model sharing.
5
- * Avoids dynamic imports inside Harper's VM sandbox by using
6
- * globalThis.__hfe_resolve__ set during module load.
4
+ * Thin wrapper around harper-fabric-embeddings for Flair resources.
5
+ * harper-fabric-embeddings handles loading and running the embedding model
6
+ * at the Harper sub-component level — we just delegate to its exported API.
7
+ *
8
+ * On platforms where the native binary isn't available, getEmbedding()
9
+ * returns null and semantic search falls back to keyword matching.
7
10
  */
8
- const MAX_CHARS = 500;
9
- const MODELS_DIR = process.env.FLAIR_MODELS_DIR || "/tmp/flair-models";
10
- const SINGLETON_KEY = "__flair_hfe_021__";
11
- const QUEUE_KEY = "__flair_embed_queue_021__";
12
- function getSingleton() {
13
- if (!process[SINGLETON_KEY]) {
14
- process[SINGLETON_KEY] = { hfe: null, dims: 0, mode: "none", initPromise: null };
15
- }
16
- return process[SINGLETON_KEY];
17
- }
18
- function getQueue() {
19
- if (!process[QUEUE_KEY]) {
20
- process[QUEUE_KEY] = { queue: [], processing: false };
11
+ import * as hfe from "harper-fabric-embeddings";
12
+ const SINGLETON_KEY = "__flair_hfe_provider_v1__";
13
+ function getState() {
14
+ if (!globalThis[SINGLETON_KEY]) {
15
+ globalThis[SINGLETON_KEY] = { initialized: false, available: false };
21
16
  }
22
- return process[QUEUE_KEY];
17
+ return globalThis[SINGLETON_KEY];
23
18
  }
24
- // ─── Init ─────────────────────────────────────────────────────────────────────
25
19
  export async function initEmbeddings() {
26
- const s = getSingleton();
27
- if (s.mode !== "none")
20
+ const state = getState();
21
+ if (state.initialized)
28
22
  return;
29
- if (s.initPromise) {
30
- await s.initPromise;
31
- return;
32
- }
33
- s.initPromise = doInit(s);
34
- await s.initPromise;
35
- }
36
- async function doInit(s) {
37
- // Resolve path at build time relative to this file's location
38
- const hfePath = new URL("../../node_modules/harper-fabric-embeddings/dist/index.js", import.meta.url).href;
39
23
  try {
40
- // Use globalThis.process to do a native dynamic import outside the
41
- // VM sandbox's importModuleDynamically interception. The Function
42
- // constructor creates code in the global scope, not the VM context.
43
- const importFn = new Function("url", "return import(url)");
44
- const mod = await importFn(hfePath);
45
- if (typeof mod.init !== "function") {
46
- throw new Error(`Module has no init(). Keys: ${Object.keys(mod)}`);
47
- }
48
- const result = mod.init({ modelsDir: MODELS_DIR, gpuLayers: 99 });
49
- if (result?.then)
50
- await result;
51
- s.hfe = mod;
52
- s.dims = mod.dimensions();
53
- s.mode = "native";
54
- console.log(`[embeddings] Native in-process (v0.2.1): ${s.dims} dims`);
24
+ await hfe.init({});
25
+ state.available = true;
26
+ console.log(`[embeddings] harper-fabric-embeddings ready (${hfe.dimensions()} dims)`);
55
27
  }
56
28
  catch (err) {
57
- console.error(`[embeddings] Native load failed: ${err.message}`);
58
- // Fallback to hash-based embeddings
59
- try {
60
- s.dims = 512;
61
- s.mode = "hash";
62
- console.log(`[embeddings] Fallback: 512 dims (hash-based)`);
63
- }
64
- catch (e2) {
65
- console.error(`[embeddings] All embedding modes failed: ${e2.message}`);
66
- }
29
+ console.log(`[embeddings] harper-fabric-embeddings unavailable: ${err.message}`);
30
+ state.available = false;
67
31
  }
32
+ state.initialized = true;
68
33
  }
69
- // ─── Public API ───────────────────────────────────────────────────────────────
70
- export function getDimensions() { return getSingleton().dims; }
71
- export function getMode() { return getSingleton().mode; }
72
34
  export async function getEmbedding(text) {
73
- const s = getSingleton();
74
- if (s.mode === "none")
35
+ const state = getState();
36
+ if (!state.initialized)
75
37
  await initEmbeddings();
76
- return new Promise((resolve) => {
77
- const q = getQueue();
78
- q.queue.push({ text, resolve });
79
- processQueue(q);
80
- });
81
- }
82
- export function getQueueLength() { return getQueue().queue.length; }
83
- async function processQueue(q) {
84
- if (q.processing)
85
- return;
86
- q.processing = true;
87
- while (q.queue.length > 0) {
88
- const job = q.queue.shift();
89
- try {
90
- const s = getSingleton();
91
- if (s.mode === "native" && s.hfe) {
92
- job.resolve(await s.hfe.embed(job.text.slice(0, MAX_CHARS)));
93
- }
94
- else if (s.mode === "hash") {
95
- // Hash fallback doesn't need the module
96
- const text = job.text.slice(0, MAX_CHARS);
97
- job.resolve(fallbackEmbed(text));
98
- }
99
- else {
100
- job.resolve(null);
101
- }
102
- }
103
- catch (err) {
104
- console.error(`[embeddings] embed failed: ${err.message}`);
105
- job.resolve(null);
106
- }
38
+ if (!state.available)
39
+ return null;
40
+ try {
41
+ return hfe.embed(text);
107
42
  }
108
- q.processing = false;
109
- }
110
- // ─── Hash Fallback ────────────────────────────────────────────────────────────
111
- function fallbackEmbed(text) {
112
- const dims = 512;
113
- const vec = new Array(dims).fill(0);
114
- for (let i = 0; i < text.length; i++) {
115
- const code = text.charCodeAt(i);
116
- vec[i % dims] += code / 128;
117
- vec[(i * 7 + 3) % dims] += Math.sin(code * 0.1) * 0.5;
43
+ catch (err) {
44
+ console.log(`[embeddings] embed failed: ${err.message}`);
45
+ return null;
118
46
  }
119
- // Normalize
120
- let mag = 0;
121
- for (let i = 0; i < dims; i++)
122
- mag += vec[i] * vec[i];
123
- mag = Math.sqrt(mag) || 1;
124
- for (let i = 0; i < dims; i++)
125
- vec[i] /= mag;
126
- return vec;
47
+ }
48
+ export function getMode() {
49
+ const state = getState();
50
+ return state.available ? "local" : "none";
127
51
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.3.1",
4
- "description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality \u2014 all in a single process.",
3
+ "version": "0.3.4",
4
+ "description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality all in a single process.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -50,7 +50,6 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@harperfast/harper": "5.0.0-beta.4",
53
- "@node-llama-cpp/mac-arm64-metal": "^3.17.1",
54
53
  "commander": "14.0.3",
55
54
  "harper-fabric-embeddings": "^0.2.0",
56
55
  "tweetnacl": "1.0.3"
@@ -66,5 +65,8 @@
66
65
  "workspaces": [
67
66
  "packages/*",
68
67
  "plugins/*"
69
- ]
68
+ ],
69
+ "optionalDependencies": {
70
+ "@node-llama-cpp/mac-arm64-metal": "^3.17.1"
71
+ }
70
72
  }
@@ -1,144 +1,57 @@
1
1
  /**
2
- * In-process embeddings via harper-fabric-embeddings (v0.2.1+).
2
+ * embeddings-provider.ts
3
3
  *
4
- * Uses process-level singleton for cross-thread model sharing.
5
- * Avoids dynamic imports inside Harper's VM sandbox by using
6
- * globalThis.__hfe_resolve__ set during module load.
4
+ * Thin wrapper around harper-fabric-embeddings for Flair resources.
5
+ * harper-fabric-embeddings handles loading and running the embedding model
6
+ * at the Harper sub-component level — we just delegate to its exported API.
7
+ *
8
+ * On platforms where the native binary isn't available, getEmbedding()
9
+ * returns null and semantic search falls back to keyword matching.
7
10
  */
8
11
 
9
- const MAX_CHARS = 500;
10
- const MODELS_DIR = process.env.FLAIR_MODELS_DIR || "/tmp/flair-models";
11
- const SINGLETON_KEY = "__flair_hfe_021__";
12
- const QUEUE_KEY = "__flair_embed_queue_021__";
13
-
14
- interface HfeSingleton {
15
- hfe: any;
16
- dims: number;
17
- mode: "native" | "hash" | "none";
18
- initPromise: Promise<void> | null;
19
- }
12
+ import * as hfe from "harper-fabric-embeddings";
20
13
 
21
- function getSingleton(): HfeSingleton {
22
- if (!(process as any)[SINGLETON_KEY]) {
23
- (process as any)[SINGLETON_KEY] = { hfe: null, dims: 0, mode: "none", initPromise: null };
24
- }
25
- return (process as any)[SINGLETON_KEY];
26
- }
14
+ const SINGLETON_KEY = "__flair_hfe_provider_v1__";
27
15
 
28
- interface QueueState {
29
- queue: Array<{ text: string; resolve: (v: number[] | null) => void }>;
30
- processing: boolean;
16
+ interface ProviderState {
17
+ initialized: boolean;
18
+ available: boolean;
31
19
  }
32
20
 
33
- function getQueue(): QueueState {
34
- if (!(process as any)[QUEUE_KEY]) {
35
- (process as any)[QUEUE_KEY] = { queue: [], processing: false };
21
+ function getState(): ProviderState {
22
+ if (!(globalThis as any)[SINGLETON_KEY]) {
23
+ (globalThis as any)[SINGLETON_KEY] = { initialized: false, available: false };
36
24
  }
37
- return (process as any)[QUEUE_KEY];
25
+ return (globalThis as any)[SINGLETON_KEY];
38
26
  }
39
27
 
40
- // ─── Init ─────────────────────────────────────────────────────────────────────
41
-
42
28
  export async function initEmbeddings(): Promise<void> {
43
- const s = getSingleton();
44
- if (s.mode !== "none") return;
45
- if (s.initPromise) { await s.initPromise; return; }
46
- s.initPromise = doInit(s);
47
- await s.initPromise;
48
- }
49
-
50
- async function doInit(s: HfeSingleton): Promise<void> {
51
- // Resolve path at build time relative to this file's location
52
- const hfePath = new URL(
53
- "../../node_modules/harper-fabric-embeddings/dist/index.js",
54
- import.meta.url
55
- ).href;
56
-
29
+ const state = getState();
30
+ if (state.initialized) return;
57
31
  try {
58
- // Use globalThis.process to do a native dynamic import outside the
59
- // VM sandbox's importModuleDynamically interception. The Function
60
- // constructor creates code in the global scope, not the VM context.
61
- const importFn = new Function("url", "return import(url)") as (url: string) => Promise<any>;
62
- const mod = await importFn(hfePath);
63
-
64
- if (typeof mod.init !== "function") {
65
- throw new Error(`Module has no init(). Keys: ${Object.keys(mod)}`);
66
- }
67
- const result = mod.init({ modelsDir: MODELS_DIR, gpuLayers: 99 });
68
- if (result?.then) await result;
69
-
70
- s.hfe = mod;
71
- s.dims = mod.dimensions();
72
- s.mode = "native";
73
- console.log(`[embeddings] Native in-process (v0.2.1): ${s.dims} dims`);
32
+ await hfe.init({});
33
+ state.available = true;
34
+ console.log(`[embeddings] harper-fabric-embeddings ready (${hfe.dimensions()} dims)`);
74
35
  } catch (err: any) {
75
- console.error(`[embeddings] Native load failed: ${err.message}`);
76
- // Fallback to hash-based embeddings
77
- try {
78
- s.dims = 512; s.mode = "hash";
79
- console.log(`[embeddings] Fallback: 512 dims (hash-based)`);
80
- } catch (e2: any) {
81
- console.error(`[embeddings] All embedding modes failed: ${e2.message}`);
82
- }
36
+ console.log(`[embeddings] harper-fabric-embeddings unavailable: ${err.message}`);
37
+ state.available = false;
83
38
  }
39
+ state.initialized = true;
84
40
  }
85
41
 
86
- // ─── Public API ───────────────────────────────────────────────────────────────
87
-
88
- export function getDimensions(): number { return getSingleton().dims; }
89
- export function getMode(): string { return getSingleton().mode; }
90
-
91
42
  export async function getEmbedding(text: string): Promise<number[] | null> {
92
- const s = getSingleton();
93
- if (s.mode === "none") await initEmbeddings();
94
-
95
- return new Promise<number[] | null>((resolve) => {
96
- const q = getQueue();
97
- q.queue.push({ text, resolve });
98
- processQueue(q);
99
- });
100
- }
101
-
102
- export function getQueueLength(): number { return getQueue().queue.length; }
103
-
104
- async function processQueue(q: QueueState): Promise<void> {
105
- if (q.processing) return;
106
- q.processing = true;
107
- while (q.queue.length > 0) {
108
- const job = q.queue.shift()!;
109
- try {
110
- const s = getSingleton();
111
- if (s.mode === "native" && s.hfe) {
112
- job.resolve(await s.hfe.embed(job.text.slice(0, MAX_CHARS)));
113
- } else if (s.mode === "hash") {
114
- // Hash fallback doesn't need the module
115
- const text = job.text.slice(0, MAX_CHARS);
116
- job.resolve(fallbackEmbed(text));
117
- } else {
118
- job.resolve(null);
119
- }
120
- } catch (err: any) {
121
- console.error(`[embeddings] embed failed: ${err.message}`);
122
- job.resolve(null);
123
- }
43
+ const state = getState();
44
+ if (!state.initialized) await initEmbeddings();
45
+ if (!state.available) return null;
46
+ try {
47
+ return hfe.embed(text);
48
+ } catch (err: any) {
49
+ console.log(`[embeddings] embed failed: ${err.message}`);
50
+ return null;
124
51
  }
125
- q.processing = false;
126
52
  }
127
53
 
128
- // ─── Hash Fallback ────────────────────────────────────────────────────────────
129
-
130
- function fallbackEmbed(text: string): number[] {
131
- const dims = 512;
132
- const vec = new Array(dims).fill(0);
133
- for (let i = 0; i < text.length; i++) {
134
- const code = text.charCodeAt(i);
135
- vec[i % dims] += code / 128;
136
- vec[(i * 7 + 3) % dims] += Math.sin(code * 0.1) * 0.5;
137
- }
138
- // Normalize
139
- let mag = 0;
140
- for (let i = 0; i < dims; i++) mag += vec[i] * vec[i];
141
- mag = Math.sqrt(mag) || 1;
142
- for (let i = 0; i < dims; i++) vec[i] /= mag;
143
- return vec;
54
+ export function getMode(): "local" | "none" {
55
+ const state = getState();
56
+ return state.available ? "local" : "none";
144
57
  }