brainbank 0.1.3 → 0.1.5

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 (167) hide show
  1. package/README.md +84 -1107
  2. package/assets/architecture.png +0 -0
  3. package/bin/brainbank +8 -1
  4. package/bin/brainbank-mcp +19 -0
  5. package/dist/chunk-3UIWA32X.js +3341 -0
  6. package/dist/chunk-3UIWA32X.js.map +1 -0
  7. package/dist/chunk-3YBCD6DI.js +117 -0
  8. package/dist/chunk-3YBCD6DI.js.map +1 -0
  9. package/dist/chunk-DAGVUEXL.js +258 -0
  10. package/dist/chunk-DAGVUEXL.js.map +1 -0
  11. package/dist/chunk-DMFMTOHF.js +123 -0
  12. package/dist/chunk-DMFMTOHF.js.map +1 -0
  13. package/dist/chunk-FQYKWB2Q.js +136 -0
  14. package/dist/chunk-FQYKWB2Q.js.map +1 -0
  15. package/dist/chunk-IMJJ2VEM.js +74 -0
  16. package/dist/chunk-IMJJ2VEM.js.map +1 -0
  17. package/dist/chunk-M744PCJQ.js +43 -0
  18. package/dist/chunk-M744PCJQ.js.map +1 -0
  19. package/dist/chunk-NNDY7P2R.js +211 -0
  20. package/dist/chunk-NNDY7P2R.js.map +1 -0
  21. package/dist/chunk-O3J6ZIXK.js +82 -0
  22. package/dist/chunk-O3J6ZIXK.js.map +1 -0
  23. package/dist/chunk-RDQYDLYZ.js +69 -0
  24. package/dist/chunk-RDQYDLYZ.js.map +1 -0
  25. package/dist/chunk-WCQVDF3K.js +14 -0
  26. package/dist/cli.js +2713 -325
  27. package/dist/cli.js.map +1 -1
  28. package/dist/haiku-pruner-5KVT5AI2.js +8 -0
  29. package/dist/http-server-2ZQ6I43B.js +9 -0
  30. package/dist/index.d.ts +1886 -626
  31. package/dist/index.js +319 -46
  32. package/dist/index.js.map +1 -1
  33. package/dist/local-embedding-NZQTILGV.js +8 -0
  34. package/dist/mcp.d.ts +2 -0
  35. package/dist/mcp.js +333 -0
  36. package/dist/mcp.js.map +1 -0
  37. package/dist/openai-embedding-ZP5TSUJG.js +8 -0
  38. package/dist/perplexity-context-embedding-GI5PHE6X.js +9 -0
  39. package/dist/perplexity-context-embedding-GI5PHE6X.js.map +1 -0
  40. package/dist/perplexity-embedding-KZRYGJRC.js +10 -0
  41. package/dist/perplexity-embedding-KZRYGJRC.js.map +1 -0
  42. package/dist/plugin-IKQ6IRSJ.js +32 -0
  43. package/dist/plugin-IKQ6IRSJ.js.map +1 -0
  44. package/dist/resolve-ASGLBNUC.js +10 -0
  45. package/dist/resolve-ASGLBNUC.js.map +1 -0
  46. package/dist/stats-tui-AD3AMYGV.js +1904 -0
  47. package/dist/stats-tui-AD3AMYGV.js.map +1 -0
  48. package/package.json +38 -53
  49. package/src/brainbank.ts +617 -0
  50. package/src/cli/commands/collection.ts +77 -0
  51. package/src/cli/commands/context.ts +59 -0
  52. package/src/cli/commands/daemon.ts +100 -0
  53. package/src/cli/commands/docs.ts +71 -0
  54. package/src/cli/commands/files.ts +69 -0
  55. package/src/cli/commands/help.ts +82 -0
  56. package/src/cli/commands/index.ts +478 -0
  57. package/src/cli/commands/kv.ts +140 -0
  58. package/src/cli/commands/mcp-export.ts +273 -0
  59. package/src/cli/commands/mcp.ts +6 -0
  60. package/src/cli/commands/query.ts +167 -0
  61. package/src/cli/commands/reembed.ts +30 -0
  62. package/src/cli/commands/reindex.ts +40 -0
  63. package/src/cli/commands/scan.ts +336 -0
  64. package/src/cli/commands/search.ts +203 -0
  65. package/src/cli/commands/stats.ts +68 -0
  66. package/src/cli/commands/status.ts +47 -0
  67. package/src/cli/commands/watch.ts +47 -0
  68. package/src/cli/factory/brain-context.ts +43 -0
  69. package/src/cli/factory/builtin-registration.ts +87 -0
  70. package/src/cli/factory/config-loader.ts +77 -0
  71. package/src/cli/factory/index.ts +69 -0
  72. package/src/cli/factory/plugin-loader.ts +324 -0
  73. package/src/cli/index.ts +76 -0
  74. package/src/cli/server-client.ts +186 -0
  75. package/src/cli/tui/index-tui.tsx +667 -0
  76. package/src/cli/tui/stats-data.ts +523 -0
  77. package/src/cli/tui/stats-search.ts +262 -0
  78. package/src/cli/tui/stats-tui.tsx +1465 -0
  79. package/src/cli/tui/tree-scanner.ts +650 -0
  80. package/src/cli/utils.ts +137 -0
  81. package/src/config.ts +48 -0
  82. package/src/constants.ts +21 -0
  83. package/src/db/adapter.ts +112 -0
  84. package/src/db/metadata.ts +130 -0
  85. package/src/db/migrations.ts +66 -0
  86. package/src/db/sqlite-adapter.ts +218 -0
  87. package/src/db/tracker.ts +91 -0
  88. package/src/engine/index-api.ts +81 -0
  89. package/src/engine/reembed.ts +206 -0
  90. package/src/engine/search-api.ts +218 -0
  91. package/src/index.ts +150 -0
  92. package/src/lib/fts.ts +57 -0
  93. package/src/lib/languages.ts +179 -0
  94. package/src/lib/logger.ts +126 -0
  95. package/src/lib/math.ts +87 -0
  96. package/src/lib/provider-key.ts +20 -0
  97. package/src/lib/prune.ts +72 -0
  98. package/src/lib/rrf.ts +133 -0
  99. package/src/lib/write-lock.ts +108 -0
  100. package/src/mcp/mcp-server.ts +192 -0
  101. package/src/mcp/workspace-factory.ts +68 -0
  102. package/src/mcp/workspace-pool.ts +224 -0
  103. package/src/plugin.ts +381 -0
  104. package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
  105. package/src/providers/embeddings/embedding-worker.ts +141 -0
  106. package/src/providers/embeddings/local-embedding.ts +115 -0
  107. package/src/providers/embeddings/openai-embedding.ts +167 -0
  108. package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
  109. package/src/providers/embeddings/perplexity-embedding.ts +165 -0
  110. package/src/providers/embeddings/resolve.ts +34 -0
  111. package/src/providers/pruners/haiku-expander.ts +178 -0
  112. package/src/providers/pruners/haiku-pruner.ts +263 -0
  113. package/src/providers/vector/hnsw-index.ts +174 -0
  114. package/src/providers/vector/hnsw-loader.ts +129 -0
  115. package/src/search/bm25-boost.ts +76 -0
  116. package/src/search/context-builder.ts +209 -0
  117. package/src/search/keyword/composite-bm25-search.ts +47 -0
  118. package/src/search/query-decomposer.ts +124 -0
  119. package/src/search/types.ts +37 -0
  120. package/src/search/vector/composite-vector-search.ts +105 -0
  121. package/src/search/vector/mmr.ts +64 -0
  122. package/src/services/collection.ts +384 -0
  123. package/src/services/daemon.ts +87 -0
  124. package/src/services/http-server.ts +344 -0
  125. package/src/services/kv-service.ts +64 -0
  126. package/src/services/plugin-registry.ts +77 -0
  127. package/src/services/watch.ts +340 -0
  128. package/src/services/webhook-server.ts +100 -0
  129. package/src/types.ts +509 -0
  130. package/dist/chunk-2P3EGY6S.js +0 -37
  131. package/dist/chunk-2P3EGY6S.js.map +0 -1
  132. package/dist/chunk-3GAIDXRW.js +0 -105
  133. package/dist/chunk-3GAIDXRW.js.map +0 -1
  134. package/dist/chunk-4ZKBQ33J.js +0 -56
  135. package/dist/chunk-4ZKBQ33J.js.map +0 -1
  136. package/dist/chunk-7QVYU63E.js +0 -7
  137. package/dist/chunk-GOUBW7UA.js +0 -373
  138. package/dist/chunk-GOUBW7UA.js.map +0 -1
  139. package/dist/chunk-MJ3Y24H6.js +0 -185
  140. package/dist/chunk-MJ3Y24H6.js.map +0 -1
  141. package/dist/chunk-N6ZMBFDE.js +0 -224
  142. package/dist/chunk-N6ZMBFDE.js.map +0 -1
  143. package/dist/chunk-RAEBYV75.js +0 -709
  144. package/dist/chunk-RAEBYV75.js.map +0 -1
  145. package/dist/chunk-TW5NTYYZ.js +0 -2066
  146. package/dist/chunk-TW5NTYYZ.js.map +0 -1
  147. package/dist/chunk-Z5SU54HP.js +0 -171
  148. package/dist/chunk-Z5SU54HP.js.map +0 -1
  149. package/dist/code.d.ts +0 -31
  150. package/dist/code.js +0 -8
  151. package/dist/docs.d.ts +0 -19
  152. package/dist/docs.js +0 -8
  153. package/dist/git.d.ts +0 -31
  154. package/dist/git.js +0 -8
  155. package/dist/memory.d.ts +0 -19
  156. package/dist/memory.js +0 -146
  157. package/dist/memory.js.map +0 -1
  158. package/dist/notes.d.ts +0 -19
  159. package/dist/notes.js +0 -57
  160. package/dist/notes.js.map +0 -1
  161. package/dist/openai-PCTYLOWI.js +0 -8
  162. package/dist/types-Da_zLLOl.d.ts +0 -474
  163. /package/dist/{chunk-7QVYU63E.js.map → chunk-WCQVDF3K.js.map} +0 -0
  164. /package/dist/{code.js.map → haiku-pruner-5KVT5AI2.js.map} +0 -0
  165. /package/dist/{docs.js.map → http-server-2ZQ6I43B.js.map} +0 -0
  166. /package/dist/{git.js.map → local-embedding-NZQTILGV.js.map} +0 -0
  167. /package/dist/{openai-PCTYLOWI.js.map → openai-embedding-ZP5TSUJG.js.map} +0 -0
@@ -0,0 +1,117 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-WCQVDF3K.js";
4
+
5
+ // src/providers/embeddings/perplexity-embedding.ts
6
+ var DEFAULT_MODEL = "pplx-embed-v1-4b";
7
+ var DEFAULT_DIMS = {
8
+ "pplx-embed-v1-0.6b": 1024,
9
+ "pplx-embed-v1-4b": 2560
10
+ };
11
+ var API_URL = "https://api.perplexity.ai/v1/embeddings";
12
+ var MAX_BATCH = 100;
13
+ var REQUEST_TIMEOUT_MS = 3e4;
14
+ var BATCH_DELAY_MS = 100;
15
+ var PerplexityEmbedding = class {
16
+ static {
17
+ __name(this, "PerplexityEmbedding");
18
+ }
19
+ dims;
20
+ _apiKey;
21
+ _model;
22
+ _baseUrl;
23
+ _requestDims;
24
+ _timeout;
25
+ constructor(options = {}) {
26
+ this._apiKey = options.apiKey ?? process.env.PERPLEXITY_API_KEY ?? "";
27
+ this._model = options.model ?? DEFAULT_MODEL;
28
+ this._baseUrl = options.baseUrl ?? API_URL;
29
+ this._timeout = options.timeout ?? REQUEST_TIMEOUT_MS;
30
+ if (options.dims) {
31
+ this._requestDims = options.dims;
32
+ this.dims = options.dims;
33
+ } else {
34
+ this.dims = DEFAULT_DIMS[this._model] ?? 2560;
35
+ }
36
+ }
37
+ async embed(text) {
38
+ const results = await this._request([text]);
39
+ return results[0];
40
+ }
41
+ async embedBatch(texts) {
42
+ if (texts.length === 0) return [];
43
+ const results = [];
44
+ for (let i = 0; i < texts.length; i += MAX_BATCH) {
45
+ if (i > 0) await sleep(BATCH_DELAY_MS);
46
+ const batch = texts.slice(i, i + MAX_BATCH);
47
+ const embeddings = await this._request(batch);
48
+ results.push(...embeddings);
49
+ }
50
+ return results;
51
+ }
52
+ async close() {
53
+ }
54
+ async _request(input) {
55
+ if (!this._apiKey) {
56
+ throw new Error(
57
+ "BrainBank: Perplexity API key required. Set PERPLEXITY_API_KEY env var or pass apiKey option."
58
+ );
59
+ }
60
+ const MAX_CHARS = 24e3;
61
+ const safeInput = input.map((t) => t.length > MAX_CHARS ? t.slice(0, MAX_CHARS) : t);
62
+ const body = { model: this._model, input: safeInput };
63
+ if (this._requestDims) body.dimensions = this._requestDims;
64
+ const controller = new AbortController();
65
+ const timer = setTimeout(() => controller.abort(), this._timeout);
66
+ let res;
67
+ try {
68
+ res = await fetch(this._baseUrl, {
69
+ method: "POST",
70
+ headers: {
71
+ "Content-Type": "application/json",
72
+ "Authorization": `Bearer ${this._apiKey}`
73
+ },
74
+ body: JSON.stringify(body),
75
+ signal: controller.signal
76
+ });
77
+ } catch (err) {
78
+ clearTimeout(timer);
79
+ if (err instanceof Error && err.name === "AbortError") {
80
+ throw new Error(`BrainBank: Perplexity embedding request timed out after ${this._timeout}ms.`);
81
+ }
82
+ throw err;
83
+ } finally {
84
+ clearTimeout(timer);
85
+ }
86
+ if (!res.ok) {
87
+ const errText = await res.text();
88
+ throw new Error(`BrainBank: Perplexity embedding API error (${res.status}): ${errText}`);
89
+ }
90
+ const json = await res.json();
91
+ return json.data.sort((a, b) => a.index - b.index).map((d) => decodeBase64Int8(d.embedding, this.dims));
92
+ }
93
+ };
94
+ function decodeBase64Int8(b64, expectedDims) {
95
+ const binary = atob(b64);
96
+ const bytes = new Int8Array(binary.length);
97
+ for (let i = 0; i < binary.length; i++) {
98
+ bytes[i] = binary.charCodeAt(i) << 24 >> 24;
99
+ }
100
+ const dims = Math.min(bytes.length, expectedDims);
101
+ const result = new Float32Array(dims);
102
+ for (let i = 0; i < dims; i++) {
103
+ result[i] = bytes[i];
104
+ }
105
+ return result;
106
+ }
107
+ __name(decodeBase64Int8, "decodeBase64Int8");
108
+ function sleep(ms) {
109
+ return new Promise((resolve) => setTimeout(resolve, ms));
110
+ }
111
+ __name(sleep, "sleep");
112
+
113
+ export {
114
+ PerplexityEmbedding,
115
+ decodeBase64Int8
116
+ };
117
+ //# sourceMappingURL=chunk-3YBCD6DI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/embeddings/perplexity-embedding.ts"],"sourcesContent":["/**\n * BrainBank — Perplexity Standard Embedding Provider\n *\n * Uses Perplexity's embedding API via fetch (no SDK dependency).\n * Models: pplx-embed-v1-0.6b (1024d) and pplx-embed-v1-4b (2560d).\n *\n * Perplexity returns base64-encoded signed int8 vectors by default.\n * This provider decodes them to Float32Array for HNSW compatibility.\n *\n * Usage:\n * const brain = new BrainBank({\n * embeddingProvider: new PerplexityEmbedding({ model: 'pplx-embed-v1-4b' }),\n * });\n */\n\nimport type { EmbeddingProvider } from '@/types.ts';\n\nconst DEFAULT_MODEL = 'pplx-embed-v1-4b';\nconst DEFAULT_DIMS: Record<string, number> = {\n 'pplx-embed-v1-0.6b': 1024,\n 'pplx-embed-v1-4b': 2560,\n};\nconst API_URL = 'https://api.perplexity.ai/v1/embeddings';\nconst MAX_BATCH = 100;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst BATCH_DELAY_MS = 100;\n\nexport interface PerplexityEmbeddingOptions {\n /** Perplexity API key. Falls back to PERPLEXITY_API_KEY env var. */\n apiKey?: string;\n /** Model name. Default: 'pplx-embed-v1-4b' */\n model?: string;\n /** Vector dimensions (Matryoshka reduction). If omitted, uses model default. */\n dims?: number;\n /** Base URL override. */\n baseUrl?: string;\n /** Request timeout in ms. Default: 30000 */\n timeout?: number;\n}\n\nexport class PerplexityEmbedding implements EmbeddingProvider {\n readonly dims: number;\n\n private _apiKey: string;\n private _model: string;\n private _baseUrl: string;\n private _requestDims: number | undefined;\n private _timeout: number;\n\n constructor(options: PerplexityEmbeddingOptions = {}) {\n this._apiKey = options.apiKey ?? process.env.PERPLEXITY_API_KEY ?? '';\n this._model = options.model ?? DEFAULT_MODEL;\n this._baseUrl = options.baseUrl ?? API_URL;\n this._timeout = options.timeout ?? REQUEST_TIMEOUT_MS;\n\n if (options.dims) {\n this._requestDims = options.dims;\n this.dims = options.dims;\n } else {\n this.dims = DEFAULT_DIMS[this._model] ?? 2560;\n }\n }\n\n async embed(text: string): Promise<Float32Array> {\n const results = await this._request([text]);\n return results[0];\n }\n\n async embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n\n const results: Float32Array[] = [];\n\n for (let i = 0; i < texts.length; i += MAX_BATCH) {\n if (i > 0) await sleep(BATCH_DELAY_MS);\n const batch = texts.slice(i, i + MAX_BATCH);\n const embeddings = await this._request(batch);\n results.push(...embeddings);\n }\n\n return results;\n }\n\n async close(): Promise<void> {\n // No resources to release\n }\n\n private async _request(input: string[]): Promise<Float32Array[]> {\n if (!this._apiKey) {\n throw new Error(\n 'BrainBank: Perplexity API key required. Set PERPLEXITY_API_KEY env var or pass apiKey option.',\n );\n }\n\n const MAX_CHARS = 24_000;\n const safeInput = input.map(t => t.length > MAX_CHARS ? t.slice(0, MAX_CHARS) : t);\n\n const body: Record<string, unknown> = { model: this._model, input: safeInput };\n if (this._requestDims) body.dimensions = this._requestDims;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this._timeout);\n\n let res: Response;\n try {\n res = await fetch(this._baseUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this._apiKey}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err: unknown) {\n clearTimeout(timer);\n if (err instanceof Error && err.name === 'AbortError') {\n throw new Error(`BrainBank: Perplexity embedding request timed out after ${this._timeout}ms.`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`BrainBank: Perplexity embedding API error (${res.status}): ${errText}`);\n }\n\n const json = await res.json() as PerplexityStandardResponse;\n return json.data\n .sort((a, b) => a.index - b.index)\n .map(d => decodeBase64Int8(d.embedding, this.dims));\n }\n}\n\n\ninterface PerplexityStandardResponse {\n data: Array<{ index: number; embedding: string }>;\n}\n\n\n/**\n * Decode a base64-encoded signed int8 embedding to Float32Array.\n * Perplexity returns embeddings as base64(int8[]) by default.\n */\nexport function decodeBase64Int8(b64: string, expectedDims: number): Float32Array {\n const binary = atob(b64);\n const bytes = new Int8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i) << 24 >> 24; // sign-extend to int8\n }\n\n const dims = Math.min(bytes.length, expectedDims);\n const result = new Float32Array(dims);\n for (let i = 0; i < dims; i++) {\n result[i] = bytes[i];\n }\n return result;\n}\n\n/** Simple delay helper. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,258 @@
1
+ import {
2
+ DEFAULT_PORT,
3
+ removePid,
4
+ writePid
5
+ } from "./chunk-RDQYDLYZ.js";
6
+ import {
7
+ __name
8
+ } from "./chunk-WCQVDF3K.js";
9
+
10
+ // src/services/http-server.ts
11
+ import * as http from "http";
12
+ var SimplePool = class _SimplePool {
13
+ static {
14
+ __name(this, "SimplePool");
15
+ }
16
+ _pool = /* @__PURE__ */ new Map();
17
+ _factory;
18
+ _onError;
19
+ _timer;
20
+ static TTL_MS = 30 * 60 * 1e3;
21
+ // 30 minutes
22
+ static EVICT_INTERVAL_MS = 5 * 60 * 1e3;
23
+ // 5 minutes
24
+ constructor(options) {
25
+ this._factory = options.factory;
26
+ this._onError = options.onError;
27
+ this._timer = setInterval(() => this._evictStale(), _SimplePool.EVICT_INTERVAL_MS);
28
+ if (this._timer.unref) this._timer.unref();
29
+ }
30
+ async get(repoPath) {
31
+ const key = repoPath.replace(/\/+$/, "");
32
+ const existing = this._pool.get(key);
33
+ if (existing) {
34
+ existing.lastAccess = Date.now();
35
+ try {
36
+ await existing.brain.ensureFresh();
37
+ } catch {
38
+ }
39
+ return existing.brain;
40
+ }
41
+ const brain = await this._factory(key);
42
+ this._pool.set(key, { brain, lastAccess: Date.now() });
43
+ return brain;
44
+ }
45
+ close() {
46
+ clearInterval(this._timer);
47
+ for (const [key, entry] of this._pool) {
48
+ try {
49
+ entry.brain.close();
50
+ } catch (err) {
51
+ this._onError?.(key, err);
52
+ }
53
+ }
54
+ this._pool.clear();
55
+ }
56
+ get size() {
57
+ return this._pool.size;
58
+ }
59
+ _evictStale() {
60
+ const cutoff = Date.now() - _SimplePool.TTL_MS;
61
+ for (const [key, entry] of this._pool) {
62
+ if (entry.lastAccess < cutoff) {
63
+ try {
64
+ entry.brain.close();
65
+ } catch (err) {
66
+ this._onError?.(key, err);
67
+ }
68
+ this._pool.delete(key);
69
+ }
70
+ }
71
+ }
72
+ };
73
+ var HttpServer = class {
74
+ static {
75
+ __name(this, "HttpServer");
76
+ }
77
+ _server = null;
78
+ _pool;
79
+ _port;
80
+ _defaultRepo;
81
+ _startTime = 0;
82
+ _log;
83
+ constructor(options) {
84
+ this._port = options.port ?? DEFAULT_PORT;
85
+ this._defaultRepo = options.defaultRepo ?? process.cwd();
86
+ this._log = options.onLog ?? console.log;
87
+ this._pool = new SimplePool({
88
+ factory: options.factory,
89
+ onError: options.onError
90
+ });
91
+ }
92
+ /** Start listening. Writes PID file for daemon detection. */
93
+ start() {
94
+ return new Promise((resolve, reject) => {
95
+ this._server = http.createServer((req, res) => {
96
+ this._handleRequest(req, res).catch((err) => {
97
+ const msg = err instanceof Error ? err.message : String(err);
98
+ this._log(`Request error: ${msg}`);
99
+ if (!res.headersSent) {
100
+ res.writeHead(500, { "Content-Type": "application/json" });
101
+ res.end(JSON.stringify({ error: "Internal server error" }));
102
+ }
103
+ });
104
+ });
105
+ this._server.on("error", (err) => {
106
+ if (err.code === "EADDRINUSE") {
107
+ reject(new Error(`Port ${this._port} already in use. Is another server running?`));
108
+ } else {
109
+ reject(err);
110
+ }
111
+ });
112
+ this._server.listen(this._port, "127.0.0.1", () => {
113
+ this._startTime = Date.now();
114
+ writePid(process.pid, this._port);
115
+ this._log(`BrainBank HTTP server listening on http://localhost:${this._port}`);
116
+ resolve();
117
+ });
118
+ });
119
+ }
120
+ /** Stop the server and clean up. */
121
+ close() {
122
+ this._pool.close();
123
+ this._server?.close();
124
+ this._server = null;
125
+ removePid();
126
+ }
127
+ get port() {
128
+ return this._port;
129
+ }
130
+ // ── Request Router ──────────────────────────────────
131
+ async _handleRequest(req, res) {
132
+ res.setHeader("Content-Type", "application/json");
133
+ if (req.method === "GET" && req.url === "/health") {
134
+ return this._handleHealth(res);
135
+ }
136
+ if (req.method !== "POST") {
137
+ res.writeHead(405);
138
+ res.end(JSON.stringify({ error: "Method not allowed" }));
139
+ return;
140
+ }
141
+ const body = await this._readBody(req);
142
+ switch (req.url) {
143
+ case "/context":
144
+ return this._handleContext(body, res);
145
+ case "/search":
146
+ return this._handleSearch(body, res, "search");
147
+ case "/hsearch":
148
+ return this._handleSearch(body, res, "hybrid");
149
+ case "/ksearch":
150
+ return this._handleSearch(body, res, "keyword");
151
+ case "/index":
152
+ return this._handleIndex(body, res);
153
+ default:
154
+ res.writeHead(404);
155
+ res.end(JSON.stringify({ error: "Not found" }));
156
+ }
157
+ }
158
+ // ── Handlers ────────────────────────────────────────
159
+ _handleHealth(res) {
160
+ const uptime = Math.round((Date.now() - this._startTime) / 1e3);
161
+ res.writeHead(200);
162
+ res.end(JSON.stringify({
163
+ ok: true,
164
+ pid: process.pid,
165
+ port: this._port,
166
+ uptime,
167
+ workspaces: this._pool.size
168
+ }));
169
+ }
170
+ async _handleContext(body, res) {
171
+ const req = body;
172
+ if (!req.task) {
173
+ res.writeHead(400);
174
+ res.end(JSON.stringify({ error: "Missing required field: task" }));
175
+ return;
176
+ }
177
+ const repoPath = req.repo ?? this._defaultRepo;
178
+ const brain = await this._pool.get(repoPath);
179
+ const base = {
180
+ code: req.codeResults ?? 20,
181
+ git: req.gitResults ?? 5
182
+ };
183
+ if (req.docsResults !== void 0) base.docs = req.docsResults;
184
+ const resolvedSources = req.sources ? { ...base, ...req.sources } : base;
185
+ const context = await brain.getContext(req.task, {
186
+ affectedFiles: req.affectedFiles,
187
+ sources: resolvedSources,
188
+ pathPrefix: req.pathPrefix,
189
+ ignorePaths: req.ignorePaths,
190
+ source: "daemon",
191
+ fields: req.fields,
192
+ context: req.context,
193
+ prunerContext: req.prunerContext
194
+ });
195
+ res.writeHead(200);
196
+ res.end(JSON.stringify({ context }));
197
+ }
198
+ async _handleIndex(body, res) {
199
+ const req = body;
200
+ const repoPath = req.repo ?? this._defaultRepo;
201
+ const brain = await this._pool.get(repoPath);
202
+ const result = await brain.index({ forceReindex: req.forceReindex });
203
+ res.writeHead(200);
204
+ res.end(JSON.stringify({ result }));
205
+ }
206
+ async _handleSearch(body, res, mode) {
207
+ const req = body;
208
+ if (!req.query) {
209
+ res.writeHead(400);
210
+ res.end(JSON.stringify({ error: "Missing required field: query" }));
211
+ return;
212
+ }
213
+ const repoPath = req.repo ?? this._defaultRepo;
214
+ const brain = await this._pool.get(repoPath);
215
+ const opts = {
216
+ sources: req.sources,
217
+ pathPrefix: req.pathPrefix,
218
+ source: "daemon"
219
+ };
220
+ let results;
221
+ switch (mode) {
222
+ case "search":
223
+ results = await brain.search(req.query, opts);
224
+ break;
225
+ case "hybrid":
226
+ results = await brain.hybridSearch(req.query, opts);
227
+ break;
228
+ case "keyword":
229
+ results = await brain.searchBM25(req.query, opts);
230
+ break;
231
+ }
232
+ const maxResults = req.maxResults ?? 20;
233
+ results = results.slice(0, maxResults);
234
+ res.writeHead(200);
235
+ res.end(JSON.stringify({ results }));
236
+ }
237
+ // ── Helpers ─────────────────────────────────────────
238
+ _readBody(req) {
239
+ return new Promise((resolve, reject) => {
240
+ const chunks = [];
241
+ req.on("data", (chunk) => chunks.push(chunk));
242
+ req.on("error", reject);
243
+ req.on("end", () => {
244
+ try {
245
+ const raw = Buffer.concat(chunks).toString("utf8");
246
+ resolve(raw ? JSON.parse(raw) : {});
247
+ } catch {
248
+ resolve({});
249
+ }
250
+ });
251
+ });
252
+ }
253
+ };
254
+
255
+ export {
256
+ HttpServer
257
+ };
258
+ //# sourceMappingURL=chunk-DAGVUEXL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/http-server.ts"],"sourcesContent":["/**\n * HttpServer — Lightweight JSON API for BrainBank.\n *\n * Exposes BrainBank operations over HTTP so CLI commands can delegate\n * to a running server instead of cold-loading models each time.\n *\n * Routes:\n * POST /context → brain.getContext()\n * POST /search → brain.search()\n * POST /hsearch → brain.hybridSearch()\n * POST /ksearch → brain.searchBM25()\n * POST /index → brain.index()\n * GET /health → { ok, pid, uptime, port }\n */\n\nimport type { BrainBank } from '@/brainbank.ts';\n\nimport * as http from 'node:http';\n\nimport { DEFAULT_PORT, writePid, removePid } from './daemon.ts';\n\n// ── Types ─────────────────────────────────────────────\n\ninterface ContextRequest {\n task: string;\n repo?: string;\n sources?: Record<string, number>;\n pathPrefix?: string | string[];\n ignorePaths?: string[];\n affectedFiles?: string[];\n codeResults?: number;\n gitResults?: number;\n docsResults?: number;\n fields?: Record<string, unknown>;\n context?: string;\n prunerContext?: string;\n}\n\ninterface IndexRequest {\n repo?: string;\n forceReindex?: boolean;\n}\n\ninterface SearchRequest {\n query: string;\n repo?: string;\n sources?: Record<string, number>;\n pathPrefix?: string | string[];\n maxResults?: number;\n}\n\ninterface PoolOptions {\n /** Factory function to create a BrainBank for a repo path. */\n factory: (repoPath: string) => Promise<BrainBank>;\n /** Called when an error occurs. */\n onError?: (repo: string, err: unknown) => void;\n}\n\n// ── Simple Workspace Pool ─────────────────────────────\n\n/**\n * Minimal in-memory pool for workspace management.\n * Creates BrainBank instances on demand and caches them.\n * Eviction by TTL (30 min inactivity).\n */\nclass SimplePool {\n private _pool = new Map<string, { brain: BrainBank; lastAccess: number }>();\n private _factory: (repoPath: string) => Promise<BrainBank>;\n private _onError?: (repo: string, err: unknown) => void;\n private _timer: ReturnType<typeof setInterval>;\n\n private static readonly TTL_MS = 30 * 60 * 1000; // 30 minutes\n private static readonly EVICT_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\n\n constructor(options: PoolOptions) {\n this._factory = options.factory;\n this._onError = options.onError;\n this._timer = setInterval(() => this._evictStale(), SimplePool.EVICT_INTERVAL_MS);\n if (this._timer.unref) this._timer.unref();\n }\n\n async get(repoPath: string): Promise<BrainBank> {\n const key = repoPath.replace(/\\/+$/, '');\n const existing = this._pool.get(key);\n if (existing) {\n existing.lastAccess = Date.now();\n try { await existing.brain.ensureFresh(); } catch { /* stale is better than nothing */ }\n return existing.brain;\n }\n\n const brain = await this._factory(key);\n this._pool.set(key, { brain, lastAccess: Date.now() });\n return brain;\n }\n\n close(): void {\n clearInterval(this._timer);\n for (const [key, entry] of this._pool) {\n try { entry.brain.close(); } catch (err: unknown) {\n this._onError?.(key, err);\n }\n }\n this._pool.clear();\n }\n\n get size(): number {\n return this._pool.size;\n }\n\n private _evictStale(): void {\n const cutoff = Date.now() - SimplePool.TTL_MS;\n for (const [key, entry] of this._pool) {\n if (entry.lastAccess < cutoff) {\n try { entry.brain.close(); } catch (err: unknown) {\n this._onError?.(key, err);\n }\n this._pool.delete(key);\n }\n }\n }\n}\n\n// ── HTTP Server ───────────────────────────────────────\n\nexport interface HttpServerOptions {\n port?: number;\n /** Factory to create a BrainBank instance for a given repo path. */\n factory: (repoPath: string) => Promise<BrainBank>;\n /** Default repo path when request doesn't specify one. */\n defaultRepo?: string;\n /** Called when an error occurs during pool operations. */\n onError?: (repo: string, err: unknown) => void;\n /** Called on server lifecycle events. */\n onLog?: (msg: string) => void;\n}\n\nexport class HttpServer {\n private _server: http.Server | null = null;\n private _pool: SimplePool;\n private _port: number;\n private _defaultRepo: string;\n private _startTime = 0;\n private _log: (msg: string) => void;\n\n constructor(options: HttpServerOptions) {\n this._port = options.port ?? DEFAULT_PORT;\n this._defaultRepo = options.defaultRepo ?? process.cwd();\n this._log = options.onLog ?? console.log;\n this._pool = new SimplePool({\n factory: options.factory,\n onError: options.onError,\n });\n }\n\n /** Start listening. Writes PID file for daemon detection. */\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this._server = http.createServer((req, res) => {\n this._handleRequest(req, res).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err);\n this._log(`Request error: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n });\n\n this._server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${this._port} already in use. Is another server running?`));\n } else {\n reject(err);\n }\n });\n\n this._server.listen(this._port, '127.0.0.1', () => {\n this._startTime = Date.now();\n writePid(process.pid, this._port);\n this._log(`BrainBank HTTP server listening on http://localhost:${this._port}`);\n resolve();\n });\n });\n }\n\n /** Stop the server and clean up. */\n close(): void {\n this._pool.close();\n this._server?.close();\n this._server = null;\n removePid();\n }\n\n get port(): number {\n return this._port;\n }\n\n // ── Request Router ──────────────────────────────────\n\n private async _handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n // CORS + content type\n res.setHeader('Content-Type', 'application/json');\n\n // Health check — no body parsing needed\n if (req.method === 'GET' && req.url === '/health') {\n return this._handleHealth(res);\n }\n\n if (req.method !== 'POST') {\n res.writeHead(405);\n res.end(JSON.stringify({ error: 'Method not allowed' }));\n return;\n }\n\n const body = await this._readBody(req);\n\n switch (req.url) {\n case '/context':\n return this._handleContext(body, res);\n case '/search':\n return this._handleSearch(body, res, 'search');\n case '/hsearch':\n return this._handleSearch(body, res, 'hybrid');\n case '/ksearch':\n return this._handleSearch(body, res, 'keyword');\n case '/index':\n return this._handleIndex(body, res);\n default:\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Not found' }));\n }\n }\n\n // ── Handlers ────────────────────────────────────────\n\n private _handleHealth(res: http.ServerResponse): void {\n const uptime = Math.round((Date.now() - this._startTime) / 1000);\n res.writeHead(200);\n res.end(JSON.stringify({\n ok: true,\n pid: process.pid,\n port: this._port,\n uptime,\n workspaces: this._pool.size,\n }));\n }\n\n private async _handleContext(body: unknown, res: http.ServerResponse): Promise<void> {\n const req = body as ContextRequest;\n if (!req.task) {\n res.writeHead(400);\n res.end(JSON.stringify({ error: 'Missing required field: task' }));\n return;\n }\n\n const repoPath = req.repo ?? this._defaultRepo;\n const brain = await this._pool.get(repoPath);\n\n // Build sources from explicit params, then let `sources` override\n const base: Record<string, number> = {\n code: req.codeResults ?? 20,\n git: req.gitResults ?? 5,\n };\n if (req.docsResults !== undefined) base.docs = req.docsResults;\n const resolvedSources = req.sources ? { ...base, ...req.sources } : base;\n\n const context = await brain.getContext(req.task, {\n affectedFiles: req.affectedFiles,\n sources: resolvedSources,\n pathPrefix: req.pathPrefix,\n ignorePaths: req.ignorePaths,\n source: 'daemon',\n fields: req.fields,\n context: req.context,\n prunerContext: req.prunerContext,\n });\n\n res.writeHead(200);\n res.end(JSON.stringify({ context }));\n }\n\n private async _handleIndex(body: unknown, res: http.ServerResponse): Promise<void> {\n const req = body as IndexRequest;\n const repoPath = req.repo ?? this._defaultRepo;\n const brain = await this._pool.get(repoPath);\n\n const result = await brain.index({ forceReindex: req.forceReindex });\n\n res.writeHead(200);\n res.end(JSON.stringify({ result }));\n }\n\n private async _handleSearch(\n body: unknown, res: http.ServerResponse,\n mode: 'search' | 'hybrid' | 'keyword',\n ): Promise<void> {\n const req = body as SearchRequest;\n if (!req.query) {\n res.writeHead(400);\n res.end(JSON.stringify({ error: 'Missing required field: query' }));\n return;\n }\n\n const repoPath = req.repo ?? this._defaultRepo;\n const brain = await this._pool.get(repoPath);\n\n const opts = {\n sources: req.sources,\n pathPrefix: req.pathPrefix,\n source: 'daemon' as const,\n };\n\n let results;\n switch (mode) {\n case 'search': results = await brain.search(req.query, opts); break;\n case 'hybrid': results = await brain.hybridSearch(req.query, opts); break;\n case 'keyword': results = await brain.searchBM25(req.query, opts); break;\n }\n\n const maxResults = req.maxResults ?? 20;\n results = results.slice(0, maxResults);\n\n res.writeHead(200);\n res.end(JSON.stringify({ results }));\n }\n\n // ── Helpers ─────────────────────────────────────────\n\n private _readBody(req: http.IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('error', reject);\n req.on('end', () => {\n try {\n const raw = Buffer.concat(chunks).toString('utf8');\n resolve(raw ? JSON.parse(raw) as unknown : {});\n } catch {\n resolve({});\n }\n });\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,123 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-WCQVDF3K.js";
4
+
5
+ // src/providers/embeddings/openai-embedding.ts
6
+ var DEFAULT_MODEL = "text-embedding-3-small";
7
+ var DEFAULT_DIMS = {
8
+ "text-embedding-3-small": 1536,
9
+ "text-embedding-3-large": 3072,
10
+ "text-embedding-ada-002": 1536
11
+ };
12
+ var API_URL = "https://api.openai.com/v1/embeddings";
13
+ var MAX_BATCH = 100;
14
+ var REQUEST_TIMEOUT_MS = 3e4;
15
+ var BATCH_DELAY_MS = 100;
16
+ var OpenAIEmbedding = class {
17
+ static {
18
+ __name(this, "OpenAIEmbedding");
19
+ }
20
+ dims;
21
+ _apiKey;
22
+ _model;
23
+ _baseUrl;
24
+ _requestDims;
25
+ _timeout;
26
+ constructor(options = {}) {
27
+ this._apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
28
+ this._model = options.model ?? DEFAULT_MODEL;
29
+ this._baseUrl = options.baseUrl ?? API_URL;
30
+ this._timeout = options.timeout ?? REQUEST_TIMEOUT_MS;
31
+ if (options.dims && this._model.startsWith("text-embedding-3")) {
32
+ this._requestDims = options.dims;
33
+ this.dims = options.dims;
34
+ } else {
35
+ this.dims = options.dims ?? DEFAULT_DIMS[this._model] ?? 1536;
36
+ }
37
+ }
38
+ async embed(text) {
39
+ const results = await this._request([text]);
40
+ return results[0];
41
+ }
42
+ async embedBatch(texts) {
43
+ if (texts.length === 0) return [];
44
+ const results = [];
45
+ for (let i = 0; i < texts.length; i += MAX_BATCH) {
46
+ if (i > 0) await sleep(BATCH_DELAY_MS);
47
+ const batch = texts.slice(i, i + MAX_BATCH);
48
+ const embeddings = await this._request(batch);
49
+ results.push(...embeddings);
50
+ }
51
+ return results;
52
+ }
53
+ async close() {
54
+ }
55
+ _isTokenLimitError(errText) {
56
+ return errText.includes("maximum input length") || errText.includes("maximum context length") || errText.includes("too many tokens");
57
+ }
58
+ async _request(input, retryDepth = 0) {
59
+ if (!this._apiKey) {
60
+ throw new Error("OpenAI API key required. Set OPENAI_API_KEY env var or pass apiKey option.");
61
+ }
62
+ const MAX_CHARS = 24e3;
63
+ const safeInput = input.map((t) => t.length > MAX_CHARS ? t.slice(0, MAX_CHARS) : t);
64
+ const body = {
65
+ model: this._model,
66
+ input: safeInput
67
+ };
68
+ if (this._requestDims) body.dimensions = this._requestDims;
69
+ const controller = new AbortController();
70
+ const timer = setTimeout(() => controller.abort(), this._timeout);
71
+ let res;
72
+ try {
73
+ res = await fetch(this._baseUrl, {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "application/json",
77
+ "Authorization": `Bearer ${this._apiKey}`
78
+ },
79
+ body: JSON.stringify(body),
80
+ signal: controller.signal
81
+ });
82
+ } catch (err) {
83
+ clearTimeout(timer);
84
+ if (err instanceof Error && err.name === "AbortError") {
85
+ throw new Error(`OpenAI embedding request timed out after ${this._timeout}ms.`);
86
+ }
87
+ throw err;
88
+ } finally {
89
+ clearTimeout(timer);
90
+ }
91
+ if (!res.ok) {
92
+ return this._handleApiError(res, safeInput, retryDepth);
93
+ }
94
+ const json = await res.json();
95
+ return json.data.sort((a, b) => a.index - b.index).map((d) => new Float32Array(d.embedding));
96
+ }
97
+ /** Handle API errors with token-limit retry logic. */
98
+ async _handleApiError(res, safeInput, retryDepth) {
99
+ const err = await res.text();
100
+ const isTokenLimit = res.status === 400 && this._isTokenLimitError(err);
101
+ if (isTokenLimit && safeInput.length > 1) {
102
+ const results = [];
103
+ for (const text of safeInput) {
104
+ const r = await this._request([text.slice(0, 8e3)]);
105
+ results.push(r[0]);
106
+ }
107
+ return results;
108
+ }
109
+ if (isTokenLimit && safeInput.length === 1 && retryDepth < 1) {
110
+ return this._request([safeInput[0].slice(0, 6e3)], retryDepth + 1);
111
+ }
112
+ throw new Error(`OpenAI embedding API error (${res.status}): ${err}`);
113
+ }
114
+ };
115
+ function sleep(ms) {
116
+ return new Promise((resolve) => setTimeout(resolve, ms));
117
+ }
118
+ __name(sleep, "sleep");
119
+
120
+ export {
121
+ OpenAIEmbedding
122
+ };
123
+ //# sourceMappingURL=chunk-DMFMTOHF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/embeddings/openai-embedding.ts"],"sourcesContent":["/**\n * BrainBank — OpenAI Embedding Provider\n * \n * Uses OpenAI's embedding API via fetch (no SDK dependency).\n * Supports text-embedding-3-small, text-embedding-3-large, and ada-002.\n * \n * Usage:\n * const brain = new BrainBank({\n * embeddingProvider: new OpenAIEmbedding({ model: 'text-embedding-3-small' }),\n * });\n */\n\nimport type { EmbeddingProvider } from '@/types.ts';\n\nconst DEFAULT_MODEL = 'text-embedding-3-small';\nconst DEFAULT_DIMS: Record<string, number> = {\n 'text-embedding-3-small': 1536,\n 'text-embedding-3-large': 3072,\n 'text-embedding-ada-002': 1536,\n};\nconst API_URL = 'https://api.openai.com/v1/embeddings';\nconst MAX_BATCH = 100;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst BATCH_DELAY_MS = 100;\n\nexport interface OpenAIEmbeddingOptions {\n /** OpenAI API key. Falls back to OPENAI_API_KEY env var. */\n apiKey?: string;\n /** Model name. Default: 'text-embedding-3-small' */\n model?: string;\n /** Vector dimensions. If omitted, uses model default. text-embedding-3-* supports custom dims. */\n dims?: number;\n /** Base URL override (for Azure, proxies, etc.) */\n baseUrl?: string;\n /** Request timeout in ms. Default: 30000 */\n timeout?: number;\n}\n\nexport class OpenAIEmbedding implements EmbeddingProvider {\n readonly dims: number;\n\n private _apiKey: string;\n private _model: string;\n private _baseUrl: string;\n private _requestDims: number | undefined;\n private _timeout: number;\n\n constructor(options: OpenAIEmbeddingOptions = {}) {\n this._apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? '';\n this._model = options.model ?? DEFAULT_MODEL;\n this._baseUrl = options.baseUrl ?? API_URL;\n this._timeout = options.timeout ?? REQUEST_TIMEOUT_MS;\n\n // Custom dims only supported by text-embedding-3-*\n if (options.dims && this._model.startsWith('text-embedding-3')) {\n this._requestDims = options.dims;\n this.dims = options.dims;\n } else {\n this.dims = options.dims ?? DEFAULT_DIMS[this._model] ?? 1536;\n }\n }\n\n async embed(text: string): Promise<Float32Array> {\n const results = await this._request([text]);\n return results[0];\n }\n\n async embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n\n const results: Float32Array[] = [];\n\n for (let i = 0; i < texts.length; i += MAX_BATCH) {\n if (i > 0) await sleep(BATCH_DELAY_MS);\n const batch = texts.slice(i, i + MAX_BATCH);\n const embeddings = await this._request(batch);\n results.push(...embeddings);\n }\n\n return results;\n }\n\n async close(): Promise<void> {\n // No resources to release\n }\n\n private _isTokenLimitError(errText: string): boolean {\n return errText.includes('maximum input length') ||\n errText.includes('maximum context length') ||\n errText.includes('too many tokens');\n }\n\n private async _request(input: string[], retryDepth: number = 0): Promise<Float32Array[]> {\n if (!this._apiKey) {\n throw new Error('OpenAI API key required. Set OPENAI_API_KEY env var or pass apiKey option.');\n }\n\n const MAX_CHARS = 24_000;\n const safeInput = input.map(t => t.length > MAX_CHARS ? t.slice(0, MAX_CHARS) : t);\n\n const body: { model: string; input: string[]; dimensions?: number } = {\n model: this._model, input: safeInput,\n };\n if (this._requestDims) body.dimensions = this._requestDims;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this._timeout);\n\n let res: Response;\n try {\n res = await fetch(this._baseUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this._apiKey}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err: unknown) {\n clearTimeout(timer);\n if (err instanceof Error && err.name === 'AbortError') {\n throw new Error(`OpenAI embedding request timed out after ${this._timeout}ms.`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n\n if (!res.ok) {\n return this._handleApiError(res, safeInput, retryDepth);\n }\n\n const json = await res.json() as {\n data: Array<{ embedding: number[]; index: number }>;\n };\n return json.data.sort((a, b) => a.index - b.index).map(d => new Float32Array(d.embedding));\n }\n\n /** Handle API errors with token-limit retry logic. */\n private async _handleApiError(\n res: Response, safeInput: string[], retryDepth: number,\n ): Promise<Float32Array[]> {\n const err = await res.text();\n const isTokenLimit = res.status === 400 && this._isTokenLimitError(err);\n\n // Batch token limit → retry each item individually with aggressive truncation\n if (isTokenLimit && safeInput.length > 1) {\n const results: Float32Array[] = [];\n for (const text of safeInput) {\n const r = await this._request([text.slice(0, 8_000)]);\n results.push(r[0]);\n }\n return results;\n }\n // Single item still failing → truncate to ~2k tokens (max 1 retry)\n if (isTokenLimit && safeInput.length === 1 && retryDepth < 1) {\n return this._request([safeInput[0].slice(0, 6_000)], retryDepth + 1);\n }\n throw new Error(`OpenAI embedding API error (${res.status}): ${err}`);\n }\n}\n\n/** Simple delay helper. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}