@titan-design/brain 0.3.0 → 0.5.0

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.
@@ -0,0 +1,112 @@
1
+ // src/adapters/local-embedder.ts
2
+ import { pipeline } from "@huggingface/transformers";
3
+ var LocalEmbedder = class {
4
+ model = "bge-small-en-v1.5";
5
+ dimensions = 384;
6
+ extractor = null;
7
+ async embed(texts) {
8
+ if (!this.extractor) {
9
+ this.extractor = await pipeline("feature-extraction", "Xenova/bge-small-en-v1.5", {
10
+ dtype: "q8"
11
+ });
12
+ }
13
+ const output = await this.extractor(texts, {
14
+ pooling: "mean",
15
+ normalize: true
16
+ });
17
+ return output.tolist();
18
+ }
19
+ };
20
+
21
+ // src/adapters/ollama-embedder.ts
22
+ import { Ollama } from "ollama";
23
+ var OllamaEmbedder = class {
24
+ model = "nomic-embed-text";
25
+ dimensions = 768;
26
+ client;
27
+ constructor(url) {
28
+ this.client = url ? new Ollama({ host: url }) : new Ollama();
29
+ }
30
+ async embed(texts) {
31
+ const prefixed = texts.map((t) => `search_document: ${t}`);
32
+ try {
33
+ const response = await this.client.embed({
34
+ model: this.model,
35
+ input: prefixed
36
+ });
37
+ return response.embeddings;
38
+ } catch (error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ throw new Error(`Ollama embedding failed: ${message}. Is Ollama running?`);
41
+ }
42
+ }
43
+ };
44
+
45
+ // src/adapters/remote-embedder.ts
46
+ var RemoteEmbedder = class {
47
+ constructor(url) {
48
+ this.url = url;
49
+ }
50
+ model = "nomic-embed-text";
51
+ dimensions = 768;
52
+ async embed(texts) {
53
+ const prefixed = texts.map((t) => `search_document: ${t}`);
54
+ const baseUrl = this.url.replace(/\/+$/, "");
55
+ let response;
56
+ try {
57
+ response = await fetch(`${baseUrl}/api/embed`, {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify({ model: this.model, input: prefixed }),
61
+ signal: AbortSignal.timeout(3e4)
62
+ });
63
+ } catch (err) {
64
+ if (err instanceof DOMException && err.name === "TimeoutError") {
65
+ throw new Error(`Remote embedding timed out after 30s (${baseUrl})`);
66
+ }
67
+ throw err;
68
+ }
69
+ if (!response.ok) {
70
+ throw new Error(`Remote embedding failed: ${response.status} ${response.statusText}`);
71
+ }
72
+ const data = await response.json();
73
+ return data.embeddings;
74
+ }
75
+ };
76
+
77
+ // src/adapters/index.ts
78
+ function getEmbedderInfo(backend) {
79
+ switch (backend) {
80
+ case "local":
81
+ return { model: "bge-small-en-v1.5", dimensions: 384 };
82
+ case "ollama":
83
+ return { model: "nomic-embed-text", dimensions: 768 };
84
+ case "remote":
85
+ return { model: "nomic-embed-text", dimensions: 768 };
86
+ default:
87
+ throw new Error(`Unknown embedder backend: ${backend}`);
88
+ }
89
+ }
90
+ function createEmbedder(config) {
91
+ switch (config.embedder) {
92
+ case "local":
93
+ return new LocalEmbedder();
94
+ case "ollama":
95
+ return new OllamaEmbedder(config.ollamaUrl);
96
+ case "remote":
97
+ if (!config.ollamaUrl) {
98
+ throw new Error("ollamaUrl is required when using the remote embedder");
99
+ }
100
+ return new RemoteEmbedder(config.ollamaUrl);
101
+ default:
102
+ throw new Error(`Unknown embedder backend: ${config.embedder}`);
103
+ }
104
+ }
105
+
106
+ export {
107
+ LocalEmbedder,
108
+ OllamaEmbedder,
109
+ RemoteEmbedder,
110
+ getEmbedderInfo,
111
+ createEmbedder
112
+ };
@@ -0,0 +1,66 @@
1
+ // src/services/file-scanner.ts
2
+ import { createHash } from "crypto";
3
+ import { readFile, stat, readdir } from "fs/promises";
4
+ import { join, extname } from "path";
5
+ var INDEXABLE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".csv", ".txt"]);
6
+ function hashContent(content) {
7
+ return createHash("sha256").update(content).digest("hex");
8
+ }
9
+ async function scanForChanges(rootDir, knownFiles) {
10
+ const result = {
11
+ new: [],
12
+ modified: [],
13
+ deleted: [],
14
+ unchanged: 0
15
+ };
16
+ const entries = await readdir(rootDir, { recursive: true });
17
+ const files = entries.filter((e) => {
18
+ const ext = extname(e).toLowerCase();
19
+ return INDEXABLE_EXTENSIONS.has(ext) && !e.includes("node_modules") && !e.includes(".git");
20
+ }).map((e) => join(rootDir, e));
21
+ const seen = /* @__PURE__ */ new Set();
22
+ for (const filePath of files) {
23
+ seen.add(filePath);
24
+ const fileStat = await stat(filePath);
25
+ const mtime = fileStat.mtimeMs;
26
+ const known = knownFiles.get(filePath);
27
+ if (!known) {
28
+ const content2 = await readFile(filePath, "utf-8");
29
+ const hash2 = hashContent(content2);
30
+ result.new.push({ path: filePath, hash: hash2, mtime });
31
+ continue;
32
+ }
33
+ if (mtime === known.mtime) {
34
+ result.unchanged++;
35
+ continue;
36
+ }
37
+ const content = await readFile(filePath, "utf-8");
38
+ const hash = hashContent(content);
39
+ if (hash === known.hash) {
40
+ result.unchanged++;
41
+ } else {
42
+ result.modified.push({ path: filePath, hash, mtime });
43
+ }
44
+ }
45
+ for (const [path] of knownFiles) {
46
+ if (!seen.has(path)) {
47
+ result.deleted.push(path);
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+ async function listUnsupportedFiles(rootDir) {
53
+ const entries = await readdir(rootDir, { recursive: true });
54
+ return entries.filter((e) => {
55
+ if (e.includes("node_modules") || e.includes(".git")) return false;
56
+ const ext = extname(e).toLowerCase();
57
+ return ext !== "" && !INDEXABLE_EXTENSIONS.has(ext);
58
+ }).map((e) => ({ path: join(rootDir, e), ext: extname(e).toLowerCase() }));
59
+ }
60
+
61
+ export {
62
+ INDEXABLE_EXTENSIONS,
63
+ hashContent,
64
+ scanForChanges,
65
+ listUnsupportedFiles
66
+ };