nano-brain 2026.8.6 → 2026.8.8
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/package.json +1 -1
- package/src/codebase.ts +6 -5
- package/src/providers/reranker.ts +84 -0
- package/src/server/bootstrap.ts +1 -0
- package/src/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nano-brain",
|
|
3
|
-
"version": "2026.8.
|
|
3
|
+
"version": "2026.8.8",
|
|
4
4
|
"description": "Persistent memory and code intelligence for AI coding agents. Local MCP server with self-learning hybrid search (BM25 + vector + knowledge graph + LLM reranking), automatic session ingestion, codebase indexing, and 22 tools. Learns your preferences over time. Works with OpenCode, Claude, Cursor, Windsurf, and any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/codebase.ts
CHANGED
|
@@ -322,10 +322,11 @@ export async function indexCodebase(
|
|
|
322
322
|
const content = fs.readFileSync(filePath, 'utf-8')
|
|
323
323
|
const contentSize = Buffer.byteLength(content, 'utf-8')
|
|
324
324
|
const hash = computeHash(content)
|
|
325
|
-
const
|
|
325
|
+
const docPath = filePath.startsWith(workspaceRoot + '/') ? filePath.slice(workspaceRoot.length + 1) : filePath
|
|
326
|
+
const existingDoc = store.findDocument(docPath)
|
|
326
327
|
if (existingDoc && existingDoc.hash === hash) {
|
|
327
328
|
filesSkippedUnchanged++
|
|
328
|
-
activePaths.push(
|
|
329
|
+
activePaths.push(docPath)
|
|
329
330
|
scannedFiles.push({ path: filePath, content })
|
|
330
331
|
continue
|
|
331
332
|
}
|
|
@@ -334,7 +335,7 @@ export async function indexCodebase(
|
|
|
334
335
|
if (currentStorageUsed + netIncrease > effectiveMaxSize) {
|
|
335
336
|
filesSkippedBudget++
|
|
336
337
|
if (existingDoc) {
|
|
337
|
-
activePaths.push(
|
|
338
|
+
activePaths.push(docPath)
|
|
338
339
|
}
|
|
339
340
|
continue
|
|
340
341
|
}
|
|
@@ -355,7 +356,7 @@ export async function indexCodebase(
|
|
|
355
356
|
const now = new Date().toISOString()
|
|
356
357
|
store.insertDocument({
|
|
357
358
|
collection: 'codebase',
|
|
358
|
-
path:
|
|
359
|
+
path: docPath,
|
|
359
360
|
title,
|
|
360
361
|
hash,
|
|
361
362
|
createdAt: existingDoc?.createdAt ?? now,
|
|
@@ -391,7 +392,7 @@ export async function indexCodebase(
|
|
|
391
392
|
|
|
392
393
|
currentStorageUsed += netIncrease
|
|
393
394
|
filesIndexed++
|
|
394
|
-
activePaths.push(
|
|
395
|
+
activePaths.push(docPath)
|
|
395
396
|
scannedFiles.push({ path: filePath, content })
|
|
396
397
|
} catch {
|
|
397
398
|
continue
|
|
@@ -9,11 +9,14 @@ export interface Reranker {
|
|
|
9
9
|
export interface RerankerOptions {
|
|
10
10
|
apiKey?: string;
|
|
11
11
|
model?: string;
|
|
12
|
+
provider?: 'voyageai' | 'cohere';
|
|
12
13
|
onTokenUsage?: (model: string, tokens: number) => void;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const VOYAGE_RERANK_URL = 'https://api.voyageai.com/v1/rerank';
|
|
17
|
+
const COHERE_RERANK_URL = 'https://api.cohere.com/v2/rerank';
|
|
16
18
|
const DEFAULT_MODEL = 'rerank-2.5-lite';
|
|
19
|
+
const DEFAULT_COHERE_MODEL = 'rerank-v3.5';
|
|
17
20
|
|
|
18
21
|
class VoyageAIReranker implements Reranker {
|
|
19
22
|
private apiKey: string;
|
|
@@ -89,6 +92,79 @@ class VoyageAIReranker implements Reranker {
|
|
|
89
92
|
dispose(): void {}
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
class CohereReranker implements Reranker {
|
|
96
|
+
private apiKey: string;
|
|
97
|
+
private model: string;
|
|
98
|
+
private onTokenUsage?: (model: string, tokens: number) => void;
|
|
99
|
+
|
|
100
|
+
constructor(apiKey: string, model: string, onTokenUsage?: (model: string, tokens: number) => void) {
|
|
101
|
+
this.apiKey = apiKey;
|
|
102
|
+
this.model = model;
|
|
103
|
+
this.onTokenUsage = onTokenUsage;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async rerank(query: string, documents: RerankDocument[]): Promise<RerankResult> {
|
|
107
|
+
if (documents.length === 0) {
|
|
108
|
+
return { results: [], model: this.model };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(COHERE_RERANK_URL, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
116
|
+
'Content-Type': 'application/json',
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
query,
|
|
120
|
+
documents: documents.map(d => d.text),
|
|
121
|
+
model: this.model,
|
|
122
|
+
top_n: documents.length,
|
|
123
|
+
}),
|
|
124
|
+
signal: AbortSignal.timeout(30000),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
const body = await response.text().catch(() => '');
|
|
129
|
+
log('reranker', `Cohere rerank failed: HTTP ${response.status} ${body}`, 'warn');
|
|
130
|
+
return { results: [], model: this.model };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const data = await response.json() as {
|
|
134
|
+
results: Array<{ index: number; relevance_score: number }>;
|
|
135
|
+
meta?: { billed_units?: { search_units?: number } };
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (this.onTokenUsage && data.meta?.billed_units?.search_units) {
|
|
139
|
+
this.onTokenUsage(this.model, data.meta.billed_units.search_units);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!data.results || !Array.isArray(data.results)) {
|
|
143
|
+
log('reranker', `Cohere rerank returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`, 'warn');
|
|
144
|
+
return { results: [], model: this.model };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const results = data.results
|
|
148
|
+
.filter(r => r.index >= 0 && r.index < documents.length)
|
|
149
|
+
.map(r => ({
|
|
150
|
+
file: documents[r.index].file,
|
|
151
|
+
score: r.relevance_score,
|
|
152
|
+
index: r.index,
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
log('reranker', `Cohere rerank model=${this.model} docs=${documents.length} units=${data.meta?.billed_units?.search_units ?? 1}`, 'debug');
|
|
156
|
+
|
|
157
|
+
return { results, model: this.model };
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
160
|
+
log('reranker', `Cohere rerank error: ${msg}`, 'warn');
|
|
161
|
+
return { results: [], model: this.model };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
dispose(): void {}
|
|
166
|
+
}
|
|
167
|
+
|
|
92
168
|
export async function createReranker(
|
|
93
169
|
options?: RerankerOptions
|
|
94
170
|
): Promise<Reranker | null> {
|
|
@@ -98,6 +174,14 @@ export async function createReranker(
|
|
|
98
174
|
return null;
|
|
99
175
|
}
|
|
100
176
|
|
|
177
|
+
const provider = options?.provider || 'voyageai';
|
|
178
|
+
|
|
179
|
+
if (provider === 'cohere') {
|
|
180
|
+
const model = options?.model || DEFAULT_COHERE_MODEL;
|
|
181
|
+
log('reranker', `Cohere reranker initialized model=${model}`);
|
|
182
|
+
return new CohereReranker(apiKey, model, options?.onTokenUsage);
|
|
183
|
+
}
|
|
184
|
+
|
|
101
185
|
const model = options?.model || DEFAULT_MODEL;
|
|
102
186
|
log('reranker', `VoyageAI reranker initialized model=${model}`);
|
|
103
187
|
return new VoyageAIReranker(apiKey, model, options?.onTokenUsage);
|
package/src/server/bootstrap.ts
CHANGED
|
@@ -362,6 +362,7 @@ export async function startServer(options: ServerOptions): Promise<void> {
|
|
|
362
362
|
createReranker({
|
|
363
363
|
apiKey: config?.reranker?.apiKey || config?.embedding?.apiKey,
|
|
364
364
|
model: config?.reranker?.model,
|
|
365
|
+
provider: config?.reranker?.provider as 'voyageai' | 'cohere' | undefined,
|
|
365
366
|
onTokenUsage: (model, tokens) => store.recordTokenUsage(model, tokens),
|
|
366
367
|
})
|
|
367
368
|
.then((loadedReranker) => {
|