brainbank 0.7.0 → 0.8.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.
- package/README.md +76 -1398
- package/bin/brainbank +5 -1
- package/dist/{chunk-N2OJRXSB.js → chunk-3HVCONGF.js} +1 -1
- package/dist/{chunk-N2OJRXSB.js.map → chunk-3HVCONGF.js.map} +1 -1
- package/dist/{chunk-CCXVL56V.js → chunk-3JZIM5AU.js} +6 -3
- package/dist/chunk-3JZIM5AU.js.map +1 -0
- package/dist/{chunk-6XOXM7MI.js → chunk-5KU2PP34.js} +2 -2
- package/dist/{chunk-6XOXM7MI.js.map → chunk-5KU2PP34.js.map} +1 -1
- package/dist/chunk-7JDCHUJV.js +89 -0
- package/dist/chunk-7JDCHUJV.js.map +1 -0
- package/dist/{chunk-B77KABWH.js → chunk-7T2ZCZQA.js} +17 -15
- package/dist/chunk-7T2ZCZQA.js.map +1 -0
- package/dist/chunk-E3J37GDA.js +74 -0
- package/dist/chunk-E3J37GDA.js.map +1 -0
- package/dist/chunk-JEFWMS5Z.js +217 -0
- package/dist/chunk-JEFWMS5Z.js.map +1 -0
- package/dist/chunk-JRVYSTMP.js +3256 -0
- package/dist/chunk-JRVYSTMP.js.map +1 -0
- package/dist/chunk-OLDHLOMT.js +69 -0
- package/dist/chunk-OLDHLOMT.js.map +1 -0
- package/dist/{chunk-424UFCY7.js → chunk-QTZNB6AK.js} +6 -2
- package/dist/chunk-QTZNB6AK.js.map +1 -0
- package/dist/chunk-RFF7HMP6.js +109 -0
- package/dist/chunk-RFF7HMP6.js.map +1 -0
- package/dist/{chunk-ZNLN2VWV.js → chunk-TD3TEFI3.js} +1 -1
- package/dist/chunk-TD3TEFI3.js.map +1 -0
- package/dist/cli.js +1036 -341
- package/dist/cli.js.map +1 -1
- package/dist/haiku-expander-WOVJIVXD.js +8 -0
- package/dist/haiku-pruner-DB77ZQLJ.js +8 -0
- package/dist/http-server-GIRELCCL.js +9 -0
- package/dist/index.d.ts +1774 -611
- package/dist/index.js +282 -70
- package/dist/index.js.map +1 -1
- package/dist/{local-embedding-ZIMTK6PU.js → local-embedding-2RNCC5EU.js} +2 -2
- package/dist/{openai-embedding-VQZCZQYT.js → openai-embedding-Z5I4K4CN.js} +2 -2
- package/dist/perplexity-context-embedding-V5YUMXDR.js +9 -0
- package/dist/{perplexity-embedding-227WQY4R.js → perplexity-embedding-X2S72OAC.js} +2 -2
- package/dist/plugin-FF4Q34TI.js +32 -0
- package/dist/{qwen3-reranker-3MHEENT5.js → qwen3-reranker-HVIQOLKS.js} +2 -2
- package/dist/{resolve-CUJWY6HP.js → resolve-Q5D6HECY.js} +2 -2
- package/package.json +25 -52
- package/src/brainbank.ts +620 -0
- package/src/cli/commands/collection.ts +77 -0
- package/src/cli/commands/context.ts +171 -0
- package/src/cli/commands/daemon.ts +100 -0
- package/src/cli/commands/docs.ts +71 -0
- package/src/cli/commands/files.ts +69 -0
- package/src/cli/commands/help.ts +72 -0
- package/src/cli/commands/index.ts +282 -0
- package/src/cli/commands/kv.ts +140 -0
- package/src/cli/commands/mcp.ts +13 -0
- package/src/cli/commands/reembed.ts +30 -0
- package/src/cli/commands/scan.ts +365 -0
- package/src/cli/commands/search.ts +130 -0
- package/src/cli/commands/stats.ts +44 -0
- package/src/cli/commands/status.ts +47 -0
- package/src/cli/commands/watch.ts +43 -0
- package/src/cli/factory/brain-context.ts +43 -0
- package/src/cli/factory/builtin-registration.ts +123 -0
- package/src/cli/factory/config-loader.ts +72 -0
- package/src/cli/factory/index.ts +65 -0
- package/src/cli/factory/plugin-loader.ts +146 -0
- package/src/cli/index.ts +63 -0
- package/src/cli/server-client.ts +135 -0
- package/src/cli/utils.ts +121 -0
- package/src/config.ts +50 -0
- package/src/constants.ts +13 -0
- package/src/db/adapter.ts +112 -0
- package/src/db/metadata.ts +130 -0
- package/src/db/migrations.ts +66 -0
- package/src/db/sqlite-adapter.ts +208 -0
- package/src/db/tracker.ts +91 -0
- package/src/engine/index-api.ts +85 -0
- package/src/engine/reembed.ts +206 -0
- package/src/engine/search-api.ts +222 -0
- package/src/index.ts +159 -0
- package/src/lib/fts.ts +57 -0
- package/src/lib/languages.ts +180 -0
- package/src/lib/logger.ts +125 -0
- package/src/lib/math.ts +87 -0
- package/src/lib/provider-key.ts +20 -0
- package/src/lib/prune.ts +71 -0
- package/src/lib/rerank.ts +33 -0
- package/src/lib/rrf.ts +133 -0
- package/src/lib/write-lock.ts +108 -0
- package/src/plugin.ts +323 -0
- package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
- package/src/providers/embeddings/embedding-worker.ts +141 -0
- package/src/providers/embeddings/local-embedding.ts +115 -0
- package/src/providers/embeddings/openai-embedding.ts +167 -0
- package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
- package/src/providers/embeddings/perplexity-embedding.ts +165 -0
- package/src/providers/embeddings/resolve.ts +34 -0
- package/src/providers/pruners/haiku-expander.ts +152 -0
- package/src/providers/pruners/haiku-pruner.ts +112 -0
- package/src/providers/rerankers/qwen3-reranker.ts +180 -0
- package/src/providers/vector/hnsw-index.ts +174 -0
- package/src/providers/vector/hnsw-loader.ts +129 -0
- package/src/search/bm25-boost.ts +61 -0
- package/src/search/context-builder.ts +298 -0
- package/src/search/keyword/composite-bm25-search.ts +62 -0
- package/src/search/types.ts +35 -0
- package/src/search/vector/composite-vector-search.ts +76 -0
- package/src/search/vector/mmr.ts +64 -0
- package/src/services/collection.ts +405 -0
- package/src/services/daemon.ts +87 -0
- package/src/services/http-server.ts +288 -0
- package/src/services/kv-service.ts +65 -0
- package/src/services/plugin-registry.ts +109 -0
- package/src/services/watch.ts +348 -0
- package/src/services/webhook-server.ts +100 -0
- package/src/types.ts +504 -0
- package/dist/base-3SNc_CeY.d.ts +0 -593
- package/dist/chunk-424UFCY7.js.map +0 -1
- package/dist/chunk-7EZR47JV.js +0 -232
- package/dist/chunk-7EZR47JV.js.map +0 -1
- package/dist/chunk-B77KABWH.js.map +0 -1
- package/dist/chunk-CCXVL56V.js.map +0 -1
- package/dist/chunk-DI3H6JVZ.js +0 -2432
- package/dist/chunk-DI3H6JVZ.js.map +0 -1
- package/dist/chunk-FGL32LUJ.js +0 -754
- package/dist/chunk-FGL32LUJ.js.map +0 -1
- package/dist/chunk-JRSKWF6K.js +0 -313
- package/dist/chunk-JRSKWF6K.js.map +0 -1
- package/dist/chunk-U2Q2XGPZ.js +0 -42
- package/dist/chunk-U2Q2XGPZ.js.map +0 -1
- package/dist/chunk-VQ27YUHH.js +0 -629
- package/dist/chunk-VQ27YUHH.js.map +0 -1
- package/dist/chunk-VVXYZIIB.js +0 -304
- package/dist/chunk-VVXYZIIB.js.map +0 -1
- package/dist/chunk-YOLKSYWK.js +0 -79
- package/dist/chunk-YOLKSYWK.js.map +0 -1
- package/dist/chunk-ZNLN2VWV.js.map +0 -1
- package/dist/code.d.ts +0 -33
- package/dist/code.js +0 -9
- package/dist/docs.d.ts +0 -21
- package/dist/docs.js +0 -9
- package/dist/git.d.ts +0 -33
- package/dist/git.js +0 -9
- package/dist/memory.d.ts +0 -17
- package/dist/memory.js +0 -9
- package/dist/notes.d.ts +0 -17
- package/dist/notes.js +0 -10
- package/dist/perplexity-context-embedding-KSVSZXMD.js +0 -9
- package/dist/resolve-CUJWY6HP.js.map +0 -1
- /package/dist/{code.js.map → haiku-expander-WOVJIVXD.js.map} +0 -0
- /package/dist/{docs.js.map → haiku-pruner-DB77ZQLJ.js.map} +0 -0
- /package/dist/{git.js.map → http-server-GIRELCCL.js.map} +0 -0
- /package/dist/{local-embedding-ZIMTK6PU.js.map → local-embedding-2RNCC5EU.js.map} +0 -0
- /package/dist/{memory.js.map → openai-embedding-Z5I4K4CN.js.map} +0 -0
- /package/dist/{notes.js.map → perplexity-context-embedding-V5YUMXDR.js.map} +0 -0
- /package/dist/{openai-embedding-VQZCZQYT.js.map → perplexity-embedding-X2S72OAC.js.map} +0 -0
- /package/dist/{perplexity-context-embedding-KSVSZXMD.js.map → plugin-FF4Q34TI.js.map} +0 -0
- /package/dist/{perplexity-embedding-227WQY4R.js.map → qwen3-reranker-HVIQOLKS.js.map} +0 -0
- /package/dist/{qwen3-reranker-3MHEENT5.js.map → resolve-Q5D6HECY.js.map} +0 -0
package/bin/brainbank
CHANGED
|
@@ -7,5 +7,9 @@ while [ -L "$SCRIPT" ]; do
|
|
|
7
7
|
[ "${SCRIPT#/}" = "$SCRIPT" ] && SCRIPT="$DIR/$SCRIPT"
|
|
8
8
|
done
|
|
9
9
|
DIR="$(cd "$(dirname "$SCRIPT")" && pwd)"
|
|
10
|
+
ROOT="$DIR/.."
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
# Tell tsx where brainbank's tsconfig is (for @/ path aliases)
|
|
13
|
+
export TSX_TSCONFIG_PATH="$ROOT/tsconfig.json"
|
|
14
|
+
|
|
15
|
+
exec "$ROOT/node_modules/.bin/tsx" "$ROOT/src/cli/index.ts" "$@"
|
|
@@ -1 +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
|
|
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":[]}
|
|
@@ -61,7 +61,10 @@ var OpenAIEmbedding = class {
|
|
|
61
61
|
}
|
|
62
62
|
const MAX_CHARS = 24e3;
|
|
63
63
|
const safeInput = input.map((t) => t.length > MAX_CHARS ? t.slice(0, MAX_CHARS) : t);
|
|
64
|
-
const body = {
|
|
64
|
+
const body = {
|
|
65
|
+
model: this._model,
|
|
66
|
+
input: safeInput
|
|
67
|
+
};
|
|
65
68
|
if (this._requestDims) body.dimensions = this._requestDims;
|
|
66
69
|
const controller = new AbortController();
|
|
67
70
|
const timer = setTimeout(() => controller.abort(), this._timeout);
|
|
@@ -78,7 +81,7 @@ var OpenAIEmbedding = class {
|
|
|
78
81
|
});
|
|
79
82
|
} catch (err) {
|
|
80
83
|
clearTimeout(timer);
|
|
81
|
-
if (err.name === "AbortError") {
|
|
84
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
82
85
|
throw new Error(`OpenAI embedding request timed out after ${this._timeout}ms.`);
|
|
83
86
|
}
|
|
84
87
|
throw err;
|
|
@@ -117,4 +120,4 @@ __name(sleep, "sleep");
|
|
|
117
120
|
export {
|
|
118
121
|
OpenAIEmbedding
|
|
119
122
|
};
|
|
120
|
-
//# sourceMappingURL=chunk-
|
|
123
|
+
//# sourceMappingURL=chunk-3JZIM5AU.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":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
decodeBase64Int8
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3HVCONGF.js";
|
|
4
4
|
import {
|
|
5
5
|
__name
|
|
6
6
|
} from "./chunk-7QVYU63E.js";
|
|
@@ -133,4 +133,4 @@ __name(sleep, "sleep");
|
|
|
133
133
|
export {
|
|
134
134
|
PerplexityContextEmbedding
|
|
135
135
|
};
|
|
136
|
-
//# sourceMappingURL=chunk-
|
|
136
|
+
//# sourceMappingURL=chunk-5KU2PP34.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/embeddings/perplexity-context-embedding.ts"],"sourcesContent":["/**\n * BrainBank — Perplexity Contextualized Embedding Provider\n *\n * Uses Perplexity's contextualized embeddings API for document-aware vectors.\n * Chunks from the same document share context, improving retrieval quality.\n *\n * Models: pplx-embed-context-v1-0.6b (1024d), pplx-embed-context-v1-4b (2560d).\n *\n * Key difference from standard: input is string[][] (docs × chunks) and the\n * response has a nested structure. This provider adapts the flat BrainBank\n * EmbeddingProvider interface to the nested Perplexity API:\n * - embed(text) → wraps as [[text]]\n * - embedBatch(texts) → wraps as [texts] (one \"document\" of related chunks)\n *\n * Usage:\n * const brain = new BrainBank({\n * embeddingProvider: new PerplexityContextEmbedding(),\n * });\n */\n\nimport type { EmbeddingProvider } from '@/types.ts';\nimport { decodeBase64Int8 } from './perplexity-embedding.ts';\n\nconst DEFAULT_MODEL = 'pplx-embed-context-v1-4b';\nconst DEFAULT_DIMS: Record<string, number> = {\n 'pplx-embed-context-v1-0.6b': 1024,\n 'pplx-embed-context-v1-4b': 2560,\n};\nconst API_URL = 'https://api.perplexity.ai/v1/contextualizedembeddings';\nconst MAX_BATCH = 100;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst BATCH_DELAY_MS = 100;\n\nexport interface PerplexityContextEmbeddingOptions {\n /** Perplexity API key. Falls back to PERPLEXITY_API_KEY env var. */\n apiKey?: string;\n /** Model name. Default: 'pplx-embed-context-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 PerplexityContextEmbedding 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: PerplexityContextEmbeddingOptions = {}) {\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 /** Embed a single text. Wraps as [[text]] for the contextualized API. */\n async embed(text: string): Promise<Float32Array> {\n const results = await this._request([[text]]);\n return results[0];\n }\n\n /**\n * Embed multiple texts as chunks of contextualized documents.\n * Splits into sub-documents to stay under Perplexity's 32k token/doc limit.\n */\n async embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n\n const docs = splitIntoDocuments(texts);\n const results: Float32Array[] = [];\n\n for (let i = 0; i < docs.length; i++) {\n if (i > 0) await sleep(BATCH_DELAY_MS);\n const embeddings = await this._request([docs[i]]);\n results.push(...embeddings);\n }\n\n return results;\n }\n\n async close(): Promise<void> {\n // No resources to release\n }\n\n /** Send a contextualized request. Input is string[][] (docs × chunks). */\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(doc =>\n doc.map(chunk => chunk.length > MAX_CHARS ? chunk.slice(0, MAX_CHARS) : chunk),\n );\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 contextualized 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 contextualized embedding API error (${res.status}): ${errText}`);\n }\n\n const json = await res.json() as PerplexityContextResponse;\n return flattenContextResponse(json, this.dims);\n }\n}\n\n
|
|
1
|
+
{"version":3,"sources":["../src/providers/embeddings/perplexity-context-embedding.ts"],"sourcesContent":["/**\n * BrainBank — Perplexity Contextualized Embedding Provider\n *\n * Uses Perplexity's contextualized embeddings API for document-aware vectors.\n * Chunks from the same document share context, improving retrieval quality.\n *\n * Models: pplx-embed-context-v1-0.6b (1024d), pplx-embed-context-v1-4b (2560d).\n *\n * Key difference from standard: input is string[][] (docs × chunks) and the\n * response has a nested structure. This provider adapts the flat BrainBank\n * EmbeddingProvider interface to the nested Perplexity API:\n * - embed(text) → wraps as [[text]]\n * - embedBatch(texts) → wraps as [texts] (one \"document\" of related chunks)\n *\n * Usage:\n * const brain = new BrainBank({\n * embeddingProvider: new PerplexityContextEmbedding(),\n * });\n */\n\nimport type { EmbeddingProvider } from '@/types.ts';\nimport { decodeBase64Int8 } from './perplexity-embedding.ts';\n\nconst DEFAULT_MODEL = 'pplx-embed-context-v1-4b';\nconst DEFAULT_DIMS: Record<string, number> = {\n 'pplx-embed-context-v1-0.6b': 1024,\n 'pplx-embed-context-v1-4b': 2560,\n};\nconst API_URL = 'https://api.perplexity.ai/v1/contextualizedembeddings';\nconst MAX_BATCH = 100;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst BATCH_DELAY_MS = 100;\n\nexport interface PerplexityContextEmbeddingOptions {\n /** Perplexity API key. Falls back to PERPLEXITY_API_KEY env var. */\n apiKey?: string;\n /** Model name. Default: 'pplx-embed-context-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 PerplexityContextEmbedding 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: PerplexityContextEmbeddingOptions = {}) {\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 /** Embed a single text. Wraps as [[text]] for the contextualized API. */\n async embed(text: string): Promise<Float32Array> {\n const results = await this._request([[text]]);\n return results[0];\n }\n\n /**\n * Embed multiple texts as chunks of contextualized documents.\n * Splits into sub-documents to stay under Perplexity's 32k token/doc limit.\n */\n async embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n\n const docs = splitIntoDocuments(texts);\n const results: Float32Array[] = [];\n\n for (let i = 0; i < docs.length; i++) {\n if (i > 0) await sleep(BATCH_DELAY_MS);\n const embeddings = await this._request([docs[i]]);\n results.push(...embeddings);\n }\n\n return results;\n }\n\n async close(): Promise<void> {\n // No resources to release\n }\n\n /** Send a contextualized request. Input is string[][] (docs × chunks). */\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(doc =>\n doc.map(chunk => chunk.length > MAX_CHARS ? chunk.slice(0, MAX_CHARS) : chunk),\n );\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 contextualized 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 contextualized embedding API error (${res.status}): ${errText}`);\n }\n\n const json = await res.json() as PerplexityContextResponse;\n return flattenContextResponse(json, this.dims);\n }\n}\n\n\ninterface PerplexityContextResponse {\n data: Array<{\n index: number;\n data: Array<{ index: number; embedding: string }>;\n }>;\n}\n\n/** Flatten nested doc → chunk response into a single flat array. */\nfunction flattenContextResponse(json: PerplexityContextResponse, dims: number): Float32Array[] {\n return json.data\n .sort((a, b) => a.index - b.index)\n .flatMap(doc =>\n doc.data\n .sort((a, b) => a.index - b.index)\n .map(chunk => decodeBase64Int8(chunk.embedding, dims)),\n );\n}\n\n/**\n * Split chunks into sub-documents that each stay under the 32k token limit.\n * Uses ~4 chars/token estimate with safety margin (~80k chars ≈ ~20k tokens).\n */\nfunction splitIntoDocuments(texts: string[]): string[][] {\n const MAX_CHARS_PER_DOC = 80_000;\n const docs: string[][] = [];\n let current: string[] = [];\n let currentChars = 0;\n\n for (const text of texts) {\n if (current.length > 0 && currentChars + text.length > MAX_CHARS_PER_DOC) {\n docs.push(current);\n current = [];\n currentChars = 0;\n }\n current.push(text);\n currentChars += text.length;\n }\n\n if (current.length > 0) docs.push(current);\n return docs;\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,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-7QVYU63E.js";
|
|
4
|
+
|
|
5
|
+
// src/providers/pruners/haiku-pruner.ts
|
|
6
|
+
var DEFAULT_MODEL = "claude-haiku-4-5-20251001";
|
|
7
|
+
var HaikuPruner = class {
|
|
8
|
+
static {
|
|
9
|
+
__name(this, "HaikuPruner");
|
|
10
|
+
}
|
|
11
|
+
_apiKey;
|
|
12
|
+
_model;
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this._apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
15
|
+
this._model = options.model ?? DEFAULT_MODEL;
|
|
16
|
+
if (!this._apiKey) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"HaikuPruner: No API key provided. Set ANTHROPIC_API_KEY env var or pass apiKey option."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async prune(query, items) {
|
|
23
|
+
if (items.length === 0) return [];
|
|
24
|
+
if (items.length === 1) return [items[0].id];
|
|
25
|
+
const itemLines = items.map((item) => {
|
|
26
|
+
const SKIP_KEYS = /* @__PURE__ */ new Set(["id", "chunkIds", "rrfScore", "filePath"]);
|
|
27
|
+
const meta = Object.entries(item.metadata).filter(([k, v]) => v !== void 0 && v !== null && !SKIP_KEYS.has(k)).map(([k, v]) => `${k}=${v}`).join(" | ");
|
|
28
|
+
return `#${item.id} ${item.filePath} | ${meta}
|
|
29
|
+
${item.preview}`;
|
|
30
|
+
}).join("\n---\n");
|
|
31
|
+
const prompt = `Query: "${query}"
|
|
32
|
+
|
|
33
|
+
Search results (full file content):
|
|
34
|
+
${itemLines}
|
|
35
|
+
|
|
36
|
+
You are a precision search filter and ranker. You have the FULL source code of each file.
|
|
37
|
+
Return a JSON array of #IDs to KEEP, ordered by relevance (most relevant FIRST).
|
|
38
|
+
|
|
39
|
+
Rules:
|
|
40
|
+
- Understand the SPECIFIC system/feature the query targets. Don't match on shared vocabulary alone.
|
|
41
|
+
Example: "snackbar toast notification" targets the toast popup system, NOT a notification center/bell icon.
|
|
42
|
+
- KEEP files that directly implement, define types for, or configure the queried system.
|
|
43
|
+
- KEEP files where the queried system is mounted, initialized, or composed into a workflow.
|
|
44
|
+
- DROP files that only CONSUME the system (e.g. a component that calls showNotification once but has 400 lines of unrelated logic).
|
|
45
|
+
- DROP files that implement a DIFFERENT system sharing similar vocabulary.
|
|
46
|
+
- DROP infrastructure/boilerplate (font loaders, theme definitions, CSS-only layout shells) unless they directly configure the queried feature.
|
|
47
|
+
- Aim for 40-70% keep rate. Returning fewer, highly relevant files is BETTER than returning many tangential ones.
|
|
48
|
+
- ORDER: core implementation \u2192 types/config \u2192 mount points \u2192 peripheral.
|
|
49
|
+
|
|
50
|
+
Respond with ONLY the JSON array. Example: [3, 0, 5, 1]`;
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
"x-api-key": this._apiKey,
|
|
57
|
+
"anthropic-version": "2023-06-01"
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
model: this._model,
|
|
61
|
+
max_tokens: 512,
|
|
62
|
+
messages: [{
|
|
63
|
+
role: "user",
|
|
64
|
+
content: prompt
|
|
65
|
+
}]
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
return items.map((i) => i.id);
|
|
70
|
+
}
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
const text = data.content?.[0]?.text ?? "";
|
|
73
|
+
const match = text.match(/\[[\d\s,]+\]/);
|
|
74
|
+
if (!match) return items.map((i) => i.id);
|
|
75
|
+
const keepIds = JSON.parse(match[0]);
|
|
76
|
+
const validIds = new Set(items.map((i) => i.id));
|
|
77
|
+
return keepIds.filter((id) => validIds.has(id));
|
|
78
|
+
} catch {
|
|
79
|
+
return items.map((i) => i.id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async close() {
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
HaikuPruner
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=chunk-7JDCHUJV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/pruners/haiku-pruner.ts"],"sourcesContent":["/**\n * BrainBank — Haiku Pruner\n *\n * LLM-based noise filter using Anthropic's Haiku 4.5 model.\n * Binary classification: for each search result, Haiku decides\n * \"relevant\" or \"noise\" based on filePath, metadata, and full\n * file content (capped at ~8K chars per item by prune.ts).\n *\n * Latency: ~300-600ms.\n */\n\nimport type { Pruner, PrunerItem } from '@/types.ts';\n\nconst DEFAULT_MODEL = 'claude-haiku-4-5-20251001';\n\nexport interface HaikuPrunerOptions {\n /** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Model to use. Default: claude-haiku-4-5-20251001 */\n model?: string;\n}\n\nexport class HaikuPruner implements Pruner {\n private readonly _apiKey: string;\n private readonly _model: string;\n\n constructor(options: HaikuPrunerOptions = {}) {\n this._apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? '';\n this._model = options.model ?? DEFAULT_MODEL;\n\n if (!this._apiKey) {\n throw new Error(\n 'HaikuPruner: No API key provided. Set ANTHROPIC_API_KEY env var or pass apiKey option.',\n );\n }\n }\n\n async prune(query: string, items: PrunerItem[]): Promise<number[]> {\n if (items.length === 0) return [];\n if (items.length === 1) return [items[0].id];\n\n const itemLines = items.map(item => {\n // Only show useful metadata fields (skip raw scores, IDs, large arrays)\n const SKIP_KEYS = new Set(['id', 'chunkIds', 'rrfScore', 'filePath']);\n const meta = Object.entries(item.metadata)\n .filter(([k, v]) => v !== undefined && v !== null && !SKIP_KEYS.has(k))\n .map(([k, v]) => `${k}=${v}`)\n .join(' | ');\n return `#${item.id} ${item.filePath} | ${meta}\\n${item.preview}`;\n }).join('\\n---\\n');\n\n const prompt =\n `Query: \"${query}\"\\n\\nSearch results (full file content):\\n${itemLines}\\n\\n` +\n `You are a precision search filter and ranker. You have the FULL source code of each file.\\n` +\n `Return a JSON array of #IDs to KEEP, ordered by relevance (most relevant FIRST).\\n\\n` +\n `Rules:\\n` +\n `- Understand the SPECIFIC system/feature the query targets. Don't match on shared vocabulary alone.\\n` +\n ` Example: \"snackbar toast notification\" targets the toast popup system, NOT a notification center/bell icon.\\n` +\n `- KEEP files that directly implement, define types for, or configure the queried system.\\n` +\n `- KEEP files where the queried system is mounted, initialized, or composed into a workflow.\\n` +\n `- DROP files that only CONSUME the system (e.g. a component that calls showNotification once but has 400 lines of unrelated logic).\\n` +\n `- DROP files that implement a DIFFERENT system sharing similar vocabulary.\\n` +\n `- DROP infrastructure/boilerplate (font loaders, theme definitions, CSS-only layout shells) unless they directly configure the queried feature.\\n` +\n `- Aim for 40-70% keep rate. Returning fewer, highly relevant files is BETTER than returning many tangential ones.\\n` +\n `- ORDER: core implementation → types/config → mount points → peripheral.\\n\\n` +\n `Respond with ONLY the JSON array. Example: [3, 0, 5, 1]`;\n\n try {\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this._apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: this._model,\n max_tokens: 512,\n messages: [{\n role: 'user',\n content: prompt,\n }],\n }),\n });\n\n if (!response.ok) {\n // API error → fail-open, return all\n return items.map(i => i.id);\n }\n\n const data = await response.json() as {\n content: { type: string; text: string }[];\n };\n\n const text = data.content?.[0]?.text ?? '';\n // Haiku may wrap in ```json ... ``` — extract any JSON array\n const match = text.match(/\\[[\\d\\s,]+\\]/);\n if (!match) return items.map(i => i.id);\n\n const keepIds = JSON.parse(match[0]) as number[];\n const validIds = new Set(items.map(i => i.id));\n return keepIds.filter(id => validIds.has(id));\n } catch {\n // Network error → fail-open, return all\n return items.map(i => i.id);\n }\n }\n\n async close(): Promise<void> {\n // No resources to release (stateless HTTP)\n }\n}\n"],"mappings":";;;;;AAaA,IAAM,gBAAgB;AASf,IAAM,cAAN,MAAoC;AAAA,EAtB3C,OAsB2C;AAAA;AAAA;AAAA,EACtB;AAAA,EACA;AAAA,EAEjB,YAAY,UAA8B,CAAC,GAAG;AAC1C,SAAK,UAAU,QAAQ,UAAU,QAAQ,IAAI,qBAAqB;AAClE,SAAK,SAAS,QAAQ,SAAS;AAE/B,QAAI,CAAC,KAAK,SAAS;AACf,YAAM,IAAI;AAAA,QACN;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,MAAM,MAAM,OAAe,OAAwC;AAC/D,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC,MAAM,CAAC,EAAE,EAAE;AAE3C,UAAM,YAAY,MAAM,IAAI,UAAQ;AAEhC,YAAM,YAAY,oBAAI,IAAI,CAAC,MAAM,YAAY,YAAY,UAAU,CAAC;AACpE,YAAM,OAAO,OAAO,QAAQ,KAAK,QAAQ,EACpC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,MAAM,UAAa,MAAM,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC,EACrE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAC3B,KAAK,KAAK;AACf,aAAO,IAAI,KAAK,EAAE,IAAI,KAAK,QAAQ,MAAM,IAAI;AAAA,EAAK,KAAK,OAAO;AAAA,IAClE,CAAC,EAAE,KAAK,SAAS;AAEjB,UAAM,SACF,WAAW,KAAK;AAAA;AAAA;AAAA,EAA6C,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAe1E,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,yCAAyC;AAAA,QAClE,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,gBAAgB;AAAA,UAChB,aAAa,KAAK;AAAA,UAClB,qBAAqB;AAAA,QACzB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACjB,OAAO,KAAK;AAAA,UACZ,YAAY;AAAA,UACZ,UAAU,CAAC;AAAA,YACP,MAAM;AAAA,YACN,SAAS;AAAA,UACb,CAAC;AAAA,QACL,CAAC;AAAA,MACL,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAEd,eAAO,MAAM,IAAI,OAAK,EAAE,EAAE;AAAA,MAC9B;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AAIjC,YAAM,OAAO,KAAK,UAAU,CAAC,GAAG,QAAQ;AAExC,YAAM,QAAQ,KAAK,MAAM,cAAc;AACvC,UAAI,CAAC,MAAO,QAAO,MAAM,IAAI,OAAK,EAAE,EAAE;AAEtC,YAAM,UAAU,KAAK,MAAM,MAAM,CAAC,CAAC;AACnC,YAAM,WAAW,IAAI,IAAI,MAAM,IAAI,OAAK,EAAE,EAAE,CAAC;AAC7C,aAAO,QAAQ,OAAO,QAAM,SAAS,IAAI,EAAE,CAAC;AAAA,IAChD,QAAQ;AAEJ,aAAO,MAAM,IAAI,OAAK,EAAE,EAAE;AAAA,IAC9B;AAAA,EACJ;AAAA,EAEA,MAAM,QAAuB;AAAA,EAE7B;AACJ;","names":[]}
|
|
@@ -2,40 +2,42 @@ import {
|
|
|
2
2
|
__name
|
|
3
3
|
} from "./chunk-7QVYU63E.js";
|
|
4
4
|
|
|
5
|
+
// src/lib/provider-key.ts
|
|
6
|
+
function providerKey(p) {
|
|
7
|
+
const name = p.constructor?.name ?? "";
|
|
8
|
+
if (name === "OpenAIEmbedding") return "openai";
|
|
9
|
+
if (name === "PerplexityEmbedding") return "perplexity";
|
|
10
|
+
if (name === "PerplexityContextEmbedding") return "perplexity-context";
|
|
11
|
+
return "local";
|
|
12
|
+
}
|
|
13
|
+
__name(providerKey, "providerKey");
|
|
14
|
+
|
|
5
15
|
// src/providers/embeddings/resolve.ts
|
|
6
16
|
async function resolveEmbedding(key) {
|
|
7
17
|
switch (key) {
|
|
8
18
|
case "openai": {
|
|
9
|
-
const { OpenAIEmbedding } = await import("./openai-embedding-
|
|
19
|
+
const { OpenAIEmbedding } = await import("./openai-embedding-Z5I4K4CN.js");
|
|
10
20
|
return new OpenAIEmbedding();
|
|
11
21
|
}
|
|
12
22
|
case "perplexity": {
|
|
13
|
-
const { PerplexityEmbedding } = await import("./perplexity-embedding-
|
|
23
|
+
const { PerplexityEmbedding } = await import("./perplexity-embedding-X2S72OAC.js");
|
|
14
24
|
return new PerplexityEmbedding();
|
|
15
25
|
}
|
|
16
26
|
case "perplexity-context": {
|
|
17
|
-
const { PerplexityContextEmbedding } = await import("./perplexity-context-embedding-
|
|
27
|
+
const { PerplexityContextEmbedding } = await import("./perplexity-context-embedding-V5YUMXDR.js");
|
|
18
28
|
return new PerplexityContextEmbedding();
|
|
19
29
|
}
|
|
20
30
|
case "local":
|
|
21
31
|
default: {
|
|
22
|
-
const { LocalEmbedding } = await import("./local-embedding-
|
|
32
|
+
const { LocalEmbedding } = await import("./local-embedding-2RNCC5EU.js");
|
|
23
33
|
return new LocalEmbedding();
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
36
|
}
|
|
27
37
|
__name(resolveEmbedding, "resolveEmbedding");
|
|
28
|
-
function providerKey(p) {
|
|
29
|
-
const name = p.constructor?.name ?? "";
|
|
30
|
-
if (name === "OpenAIEmbedding") return "openai";
|
|
31
|
-
if (name === "PerplexityEmbedding") return "perplexity";
|
|
32
|
-
if (name === "PerplexityContextEmbedding") return "perplexity-context";
|
|
33
|
-
return "local";
|
|
34
|
-
}
|
|
35
|
-
__name(providerKey, "providerKey");
|
|
36
38
|
|
|
37
39
|
export {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
providerKey,
|
|
41
|
+
resolveEmbedding
|
|
40
42
|
};
|
|
41
|
-
//# sourceMappingURL=chunk-
|
|
43
|
+
//# sourceMappingURL=chunk-7T2ZCZQA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/provider-key.ts","../src/providers/embeddings/resolve.ts"],"sourcesContent":["/**\n * BrainBank — Provider Key\n *\n * Infers a stable key from an existing EmbeddingProvider instance.\n * Lives in lib/ (Layer 0) to avoid db/ → providers/ dependency.\n */\n\nimport type { EmbeddingProvider } from '@/types.ts';\n\n/** Known embedding provider keys. */\nexport type EmbeddingKey = 'local' | 'openai' | 'perplexity' | 'perplexity-context';\n\n/** Infer a stable key from an existing provider instance. */\nexport function providerKey(p: EmbeddingProvider): EmbeddingKey {\n const name = p.constructor?.name ?? '';\n if (name === 'OpenAIEmbedding') return 'openai';\n if (name === 'PerplexityEmbedding') return 'perplexity';\n if (name === 'PerplexityContextEmbedding') return 'perplexity-context';\n return 'local';\n}\n","/**\n * BrainBank — Embedding Provider Resolver\n *\n * Resolves an EmbeddingProvider from a stored key string.\n * Used by the Initializer to auto-resolve from DB config.\n */\n\nimport type { EmbeddingProvider } from '@/types.ts';\n\n/** Re-export providerKey from lib/ (canonical location). */\nexport { providerKey, type EmbeddingKey } from '@/lib/provider-key.ts';\n\n/** Resolve an EmbeddingProvider from a key string. Lazy-loads the provider module. */\nexport async function resolveEmbedding(key: string): Promise<EmbeddingProvider> {\n switch (key) {\n case 'openai': {\n const { OpenAIEmbedding } = await import('./openai-embedding.ts');\n return new OpenAIEmbedding();\n }\n case 'perplexity': {\n const { PerplexityEmbedding } = await import('./perplexity-embedding.ts');\n return new PerplexityEmbedding();\n }\n case 'perplexity-context': {\n const { PerplexityContextEmbedding } = await import('./perplexity-context-embedding.ts');\n return new PerplexityContextEmbedding();\n }\n case 'local':\n default: {\n const { LocalEmbedding } = await import('./local-embedding.ts');\n return new LocalEmbedding();\n }\n }\n}\n"],"mappings":";;;;;AAaO,SAAS,YAAY,GAAoC;AAC5D,QAAM,OAAO,EAAE,aAAa,QAAQ;AACpC,MAAI,SAAS,kBAAmB,QAAO;AACvC,MAAI,SAAS,sBAAuB,QAAO;AAC3C,MAAI,SAAS,6BAA8B,QAAO;AAClD,SAAO;AACX;AANgB;;;ACAhB,eAAsB,iBAAiB,KAAyC;AAC5E,UAAQ,KAAK;AAAA,IACT,KAAK,UAAU;AACX,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAuB;AAChE,aAAO,IAAI,gBAAgB;AAAA,IAC/B;AAAA,IACA,KAAK,cAAc;AACf,YAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,oCAA2B;AACxE,aAAO,IAAI,oBAAoB;AAAA,IACnC;AAAA,IACA,KAAK,sBAAsB;AACvB,YAAM,EAAE,2BAA2B,IAAI,MAAM,OAAO,4CAAmC;AACvF,aAAO,IAAI,2BAA2B;AAAA,IAC1C;AAAA,IACA,KAAK;AAAA,IACL,SAAS;AACL,YAAM,EAAE,eAAe,IAAI,MAAM,OAAO,+BAAsB;AAC9D,aAAO,IAAI,eAAe;AAAA,IAC9B;AAAA,EACJ;AACJ;AApBsB;","names":[]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-7QVYU63E.js";
|
|
4
|
+
|
|
5
|
+
// src/plugin.ts
|
|
6
|
+
function isIndexable(i) {
|
|
7
|
+
return typeof i.index === "function";
|
|
8
|
+
}
|
|
9
|
+
__name(isIndexable, "isIndexable");
|
|
10
|
+
function isSearchable(i) {
|
|
11
|
+
return typeof i.search === "function";
|
|
12
|
+
}
|
|
13
|
+
__name(isSearchable, "isSearchable");
|
|
14
|
+
function isWatchable(i) {
|
|
15
|
+
return typeof i.watch === "function";
|
|
16
|
+
}
|
|
17
|
+
__name(isWatchable, "isWatchable");
|
|
18
|
+
function isDocsPlugin(i) {
|
|
19
|
+
return typeof i.addCollection === "function" && typeof i.listCollections === "function";
|
|
20
|
+
}
|
|
21
|
+
__name(isDocsPlugin, "isDocsPlugin");
|
|
22
|
+
function isCoEditPlugin(p) {
|
|
23
|
+
return "coEdits" in p && typeof p.coEdits?.suggest === "function";
|
|
24
|
+
}
|
|
25
|
+
__name(isCoEditPlugin, "isCoEditPlugin");
|
|
26
|
+
function isReembeddable(p) {
|
|
27
|
+
return typeof p.reembedConfig === "function";
|
|
28
|
+
}
|
|
29
|
+
__name(isReembeddable, "isReembeddable");
|
|
30
|
+
function isVectorSearchPlugin(p) {
|
|
31
|
+
return typeof p.createVectorSearch === "function";
|
|
32
|
+
}
|
|
33
|
+
__name(isVectorSearchPlugin, "isVectorSearchPlugin");
|
|
34
|
+
function isContextFieldPlugin(p) {
|
|
35
|
+
return typeof p.contextFields === "function";
|
|
36
|
+
}
|
|
37
|
+
__name(isContextFieldPlugin, "isContextFieldPlugin");
|
|
38
|
+
function isContextFormatterPlugin(p) {
|
|
39
|
+
return typeof p.formatContext === "function";
|
|
40
|
+
}
|
|
41
|
+
__name(isContextFormatterPlugin, "isContextFormatterPlugin");
|
|
42
|
+
function isMigratable(p) {
|
|
43
|
+
return typeof p.schemaVersion === "number" && Array.isArray(p.migrations);
|
|
44
|
+
}
|
|
45
|
+
__name(isMigratable, "isMigratable");
|
|
46
|
+
function isBM25SearchPlugin(p) {
|
|
47
|
+
return typeof p.searchBM25 === "function";
|
|
48
|
+
}
|
|
49
|
+
__name(isBM25SearchPlugin, "isBM25SearchPlugin");
|
|
50
|
+
function isExpandablePlugin(p) {
|
|
51
|
+
return typeof p.buildManifest === "function" && typeof p.resolveChunks === "function";
|
|
52
|
+
}
|
|
53
|
+
__name(isExpandablePlugin, "isExpandablePlugin");
|
|
54
|
+
function isFileResolvable(p) {
|
|
55
|
+
return typeof p.resolveFiles === "function";
|
|
56
|
+
}
|
|
57
|
+
__name(isFileResolvable, "isFileResolvable");
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
isIndexable,
|
|
61
|
+
isSearchable,
|
|
62
|
+
isWatchable,
|
|
63
|
+
isDocsPlugin,
|
|
64
|
+
isCoEditPlugin,
|
|
65
|
+
isReembeddable,
|
|
66
|
+
isVectorSearchPlugin,
|
|
67
|
+
isContextFieldPlugin,
|
|
68
|
+
isContextFormatterPlugin,
|
|
69
|
+
isMigratable,
|
|
70
|
+
isBM25SearchPlugin,
|
|
71
|
+
isExpandablePlugin,
|
|
72
|
+
isFileResolvable
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=chunk-E3J37GDA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * BrainBank — Plugin System\n * \n * Plugins are pluggable strategies that scan external data sources\n * and push content into BrainBank. Built-in plugins handle code,\n * git, and docs. Third-party frameworks (LangChain, etc.)\n * can implement custom plugins.\n * \n * import { BrainBank } from 'brainbank';\n * import { code } from 'brainbank/indexers/code';\n * \n * const brain = new BrainBank()\n * .use(code({ repoPath: '.' }));\n */\n\nimport type { DatabaseAdapter } from './db/adapter.ts';\nimport type { Migration } from './db/migrations.ts';\nimport type { IncrementalTracker } from './db/tracker.ts';\nimport type { HNSWIndex } from './providers/vector/hnsw-index.ts';\nimport type { DomainVectorSearch } from './search/types.ts';\nimport type { WebhookServer } from './services/webhook-server.ts';\nimport type {\n EmbeddingProvider, SearchResult, IndexResult, ProgressCallback,\n ResolvedConfig, DocumentCollection, ICollection,\n WatchEventHandler, WatchHandle, WatchConfig,\n ExpanderManifestItem,\n} from './types.ts';\n\n// Provided to each plugin during initialization.\n\nexport interface PluginContext {\n /** Database adapter (shared across all plugins). */\n db: DatabaseAdapter;\n /** Embedding provider (shared). */\n embedding: EmbeddingProvider;\n /** Resolved BrainBank config. */\n config: ResolvedConfig;\n /**\n * Create and initialize an HNSW index.\n * Pass `name` to enable disk persistence (recommended).\n *\n * **Private vs shared:** Use `getOrCreateSharedHnsw()` for indexes that should be\n * part of the composite search (code, git) and persisted across restarts.\n * Use `createHnsw()` for plugin-local indexes that don't participate in the\n * main search pipeline (e.g. internal similarity lookups).\n */\n createHnsw(maxElements?: number, dims?: number, name?: string): Promise<HNSWIndex>;\n /** Load existing vectors from a SQLite vectors table into an HNSW index + cache. */\n loadVectors(table: string, idCol: string, hnsw: HNSWIndex, cache: Map<number, Float32Array>): void;\n /**\n * Get or create a shared HNSW index by key.\n *\n * **HNSW sharing strategies:**\n * The `type` key determines sharing behavior. Two plugins that pass the\n * same key share one HNSW index; different keys get separate indexes.\n *\n * | Plugin type | Key passed | Sharing behavior |\n * |------------|------------------|------------------------------------------|\n * | git | `'git'` | All `git:*` repos share one HNSW |\n * | docs | `'docs'` | All docs share one HNSW |\n * | code | `this.name` | Each `code:*` repo gets its own HNSW |\n *\n * **Rule of thumb:**\n * - Same key = shared index (saves memory, single search covers all)\n * - Plugin name as key = per-repo index (avoids cross-repo noise)\n *\n * The key is also used for hot-reload (`ensureFresh`) and disk persistence\n * (`hnsw-<key>.index`), so it must match the key used in `bumpVersion()`.\n */\n getOrCreateSharedHnsw(type: string, maxElements?: number, dims?: number): Promise<{ hnsw: HNSWIndex; vecCache: Map<number, Float32Array>; isNew: boolean }>;\n /** Get or create a dynamic collection. */\n collection(name: string): ICollection;\n /**\n * Create an incremental tracker scoped to this plugin.\n * Provides `isUnchanged`, `markIndexed`, `findOrphans`, `remove`, `clear`\n * for standardized add/update/delete detection during indexing.\n */\n createTracker(): IncrementalTracker;\n /** Optional webhook server for push-based watch plugins. undefined if not configured. */\n webhookServer?: WebhookServer;\n}\n\n// Minimal contract: name + initialize. All capabilities are expressed\n// via composed interfaces below.\n\nexport interface Plugin {\n /** Unique plugin name (e.g. 'code', 'git', 'docs'). */\n readonly name: string;\n /** Initialize the plugin (create HNSW, load vectors, etc.). */\n initialize(ctx: PluginContext): Promise<void>;\n /** Return stats for this plugin. */\n stats?(): Record<string, number | string>;\n /** Clean up resources. */\n close?(): void;\n}\n\n// Implemented by plugins that support specific capabilities.\n// Use type guards below to check at runtime.\n\n/** Options accepted by IndexablePlugin.index(). */\nexport interface IndexOptions {\n forceReindex?: boolean;\n depth?: number;\n onProgress?: ProgressCallback;\n}\n\n/** Plugins that can scan and index content (code, git). */\nexport interface IndexablePlugin extends Plugin {\n index(options?: IndexOptions): Promise<IndexResult>;\n /** Incremental: re-index only specific items by ID. Falls back to index() if not implemented. */\n indexItems?(ids: string[]): Promise<IndexResult>;\n}\n\n/** Plugins that can search indexed content (docs). */\nexport interface SearchablePlugin extends Plugin {\n search(query: string, options?: Record<string, unknown>): Promise<SearchResult[]>;\n}\n\n/** Plugins that can watch their own data source for changes. */\nexport interface WatchablePlugin extends Plugin {\n /** Start watching. Plugin controls how (fs.watch, polling, webhook, etc.). */\n watch(onEvent: WatchEventHandler): WatchHandle;\n /** Optional hints for the core (debounce, batching, priority). */\n watchConfig?(): WatchConfig;\n}\n\n\n/** Check if a plugin can scan/index content. */\nexport function isIndexable(i: Plugin): i is IndexablePlugin {\n return typeof (i as IndexablePlugin).index === 'function';\n}\n\n/** Check if a plugin can search content. */\nexport function isSearchable(i: Plugin): i is SearchablePlugin {\n return typeof (i as SearchablePlugin).search === 'function';\n}\n\n/** Check if a plugin can watch its own data source. */\nexport function isWatchable(i: Plugin): i is WatchablePlugin {\n return typeof (i as WatchablePlugin).watch === 'function';\n}\n\n/** Path-specific context metadata for document collections. */\nexport interface PathContext {\n collection: string;\n path: string;\n context: string;\n}\n\n/** Plugins that manage document collections (docs). */\nexport interface DocsPlugin extends SearchablePlugin {\n addCollection(collection: DocumentCollection): void;\n removeCollection(name: string): void;\n listCollections(): DocumentCollection[];\n indexDocs(options?: { onProgress?: (collection: string, file: string, current: number, total: number) => void }): Promise<Record<string, { indexed: number; skipped: number; removed: number; chunks: number }>>;\n addContext(collection: string, path: string, context: string): void;\n listContexts(): PathContext[];\n}\n\n/** Check if a plugin manages document collections. */\nexport function isDocsPlugin(i: Plugin): i is DocsPlugin {\n return typeof (i as DocsPlugin).addCollection === 'function'\n && typeof (i as DocsPlugin).listCollections === 'function';\n}\n\n\n/** Plugin that provides co-edit suggestions (e.g. git). */\nexport interface CoEditPlugin extends Plugin {\n coEdits: {\n suggest(filePath: string, limit: number): { file: string; count: number }[];\n };\n}\n\n/** Check if a plugin provides co-edit suggestions. */\nexport function isCoEditPlugin(p: Plugin): p is CoEditPlugin {\n return 'coEdits' in p && typeof (p as CoEditPlugin).coEdits?.suggest === 'function';\n}\n\n\n/** Table descriptor for re-embedding — maps text rows to vector BLOBs. */\nexport interface ReembedTable {\n /** Human-readable name (for progress). */\n name: string;\n /** Table with text content. */\n textTable: string;\n /** Table with vector BLOBs. */\n vectorTable: string;\n /** PK column in text table. */\n idColumn: string;\n /** FK column in vector table. */\n fkColumn: string;\n /** Build the embedding text from a DB row. */\n textBuilder: (row: Record<string, unknown>) => string;\n}\n\n/** Plugins that own vector tables and can rebuild embedding text from DB rows. */\nexport interface ReembeddablePlugin extends Plugin {\n /** Table descriptor for re-embedding. */\n reembedConfig(): ReembedTable;\n}\n\n/** Check if a plugin supports re-embedding. */\nexport function isReembeddable(p: Plugin): p is ReembeddablePlugin {\n return typeof (p as ReembeddablePlugin).reembedConfig === 'function';\n}\n\n\n/** Plugin that provides a domain-specific vector search strategy. */\nexport interface VectorSearchPlugin extends Plugin {\n /** Create the domain vector search (called during SearchAPI wiring). */\n createVectorSearch(): DomainVectorSearch | undefined;\n}\n\n/** Check if a plugin provides a domain vector search. */\nexport function isVectorSearchPlugin(p: Plugin): p is VectorSearchPlugin {\n return typeof (p as VectorSearchPlugin).createVectorSearch === 'function';\n}\n\n/** Describes a configurable context field that a plugin supports. */\nexport interface ContextFieldDef {\n /** Field name (e.g. 'lines', 'callTree', 'symbols'). Must be unique per plugin. */\n name: string;\n /** Accepted value type. 'object' allows nested config like `{ depth: 3 }`. */\n type: 'boolean' | 'number' | 'object';\n /** Default value (used when not specified in config or query). */\n default: unknown;\n /** Human-readable description for CLI --help and MCP tool descriptions. */\n description: string;\n}\n\n/** Plugin that declares configurable context fields. */\nexport interface ContextFieldPlugin extends Plugin {\n /** Declare available context fields. Called once during setup. */\n contextFields(): ContextFieldDef[];\n}\n\n/** Check if a plugin declares context fields. */\nexport function isContextFieldPlugin(p: Plugin): p is ContextFieldPlugin {\n return typeof (p as ContextFieldPlugin).contextFields === 'function';\n}\n\n/** Plugin that contributes sections to the context builder output. */\nexport interface ContextFormatterPlugin extends Plugin {\n /**\n * Append formatted markdown sections to `parts`.\n * `fields` contains resolved context fields (plugin defaults ← config ← per-query).\n */\n formatContext(results: SearchResult[], parts: string[], fields: Record<string, unknown>): void;\n}\n\n/** Check if a plugin provides context formatting. */\nexport function isContextFormatterPlugin(p: Plugin): p is ContextFormatterPlugin {\n return typeof (p as ContextFormatterPlugin).formatContext === 'function';\n}\n\n\n/** Plugin that owns database tables and supports versioned migrations. */\nexport interface MigratablePlugin extends Plugin {\n /** Current schema version for this plugin. */\n readonly schemaVersion: number;\n /** Ordered list of migrations (version 1, 2, 3, …). */\n readonly migrations: Migration[];\n}\n\n/** Check if a plugin supports schema migrations. */\nexport function isMigratable(p: Plugin): p is MigratablePlugin {\n return typeof (p as MigratablePlugin).schemaVersion === 'number'\n && Array.isArray((p as MigratablePlugin).migrations);\n}\n\n\n/** Plugin that can do FTS5 keyword search on its own tables. */\nexport interface BM25SearchPlugin extends Plugin {\n /** Run BM25 keyword search. Returns scored results. */\n searchBM25(query: string, k: number, minScore?: number): SearchResult[];\n /** Rebuild the FTS5 index from the content table. */\n rebuildFTS?(): void;\n}\n\n/** Check if a plugin provides BM25 keyword search. */\nexport function isBM25SearchPlugin(p: Plugin): p is BM25SearchPlugin {\n return typeof (p as BM25SearchPlugin).searchBM25 === 'function';\n}\n\n/** Plugin that supports context expansion (provides manifest + resolves chunk IDs). */\nexport interface ExpandablePlugin extends Plugin {\n /**\n * Build a manifest of candidate chunks for LLM expansion.\n * Returns chunks from files NOT already in search results.\n *\n * @param excludeFilePaths File paths already present in search results — excluded from manifest.\n * @param excludeIds Chunk IDs already in search results — excluded from manifest.\n */\n buildManifest(excludeFilePaths: string[], excludeIds: number[]): ExpanderManifestItem[];\n /**\n * Resolve chunk IDs back into SearchResults.\n * Called after the expander selects additional IDs.\n */\n resolveChunks(ids: number[]): SearchResult[];\n}\n\n/** Check if a plugin supports context expansion. */\nexport function isExpandablePlugin(p: Plugin): p is ExpandablePlugin {\n return typeof (p as ExpandablePlugin).buildManifest === 'function'\n && typeof (p as ExpandablePlugin).resolveChunks === 'function';\n}\n\n\n/** Plugin that can resolve file paths/patterns directly to SearchResults (no search). */\nexport interface FileResolvablePlugin extends Plugin {\n /**\n * Resolve file paths, directories, and glob patterns to SearchResults.\n * Each entry is resolved: exact → directory → glob → fuzzy basename fallback.\n *\n * @param patterns - File paths, directory prefixes (trailing `/`), or glob patterns (`*`).\n */\n resolveFiles(patterns: string[]): SearchResult[];\n}\n\n/** Check if a plugin can resolve files directly. */\nexport function isFileResolvable(p: Plugin): p is FileResolvablePlugin {\n return typeof (p as FileResolvablePlugin).resolveFiles === 'function';\n}\n"],"mappings":";;;;;AAgIO,SAAS,YAAY,GAAiC;AACzD,SAAO,OAAQ,EAAsB,UAAU;AACnD;AAFgB;AAKT,SAAS,aAAa,GAAkC;AAC3D,SAAO,OAAQ,EAAuB,WAAW;AACrD;AAFgB;AAKT,SAAS,YAAY,GAAiC;AACzD,SAAO,OAAQ,EAAsB,UAAU;AACnD;AAFgB;AAsBT,SAAS,aAAa,GAA4B;AACrD,SAAO,OAAQ,EAAiB,kBAAkB,cAC3C,OAAQ,EAAiB,oBAAoB;AACxD;AAHgB;AAcT,SAAS,eAAe,GAA8B;AACzD,SAAO,aAAa,KAAK,OAAQ,EAAmB,SAAS,YAAY;AAC7E;AAFgB;AA4BT,SAAS,eAAe,GAAoC;AAC/D,SAAO,OAAQ,EAAyB,kBAAkB;AAC9D;AAFgB;AAYT,SAAS,qBAAqB,GAAoC;AACrE,SAAO,OAAQ,EAAyB,uBAAuB;AACnE;AAFgB;AAuBT,SAAS,qBAAqB,GAAoC;AACrE,SAAO,OAAQ,EAAyB,kBAAkB;AAC9D;AAFgB;AAcT,SAAS,yBAAyB,GAAwC;AAC7E,SAAO,OAAQ,EAA6B,kBAAkB;AAClE;AAFgB;AAcT,SAAS,aAAa,GAAkC;AAC3D,SAAO,OAAQ,EAAuB,kBAAkB,YACjD,MAAM,QAAS,EAAuB,UAAU;AAC3D;AAHgB;AAeT,SAAS,mBAAmB,GAAkC;AACjE,SAAO,OAAQ,EAAuB,eAAe;AACzD;AAFgB;AAsBT,SAAS,mBAAmB,GAAkC;AACjE,SAAO,OAAQ,EAAuB,kBAAkB,cACjD,OAAQ,EAAuB,kBAAkB;AAC5D;AAHgB;AAkBT,SAAS,iBAAiB,GAAsC;AACnE,SAAO,OAAQ,EAA2B,iBAAiB;AAC/D;AAFgB;","names":[]}
|