codebaxing 0.1.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/LICENSE +21 -0
- package/README.md +402 -0
- package/README.vi.md +402 -0
- package/dist/core/exceptions.d.ts +25 -0
- package/dist/core/exceptions.d.ts.map +1 -0
- package/dist/core/exceptions.js +46 -0
- package/dist/core/exceptions.js.map +1 -0
- package/dist/core/interfaces.d.ts +13 -0
- package/dist/core/interfaces.d.ts.map +1 -0
- package/dist/core/interfaces.js +5 -0
- package/dist/core/interfaces.js.map +1 -0
- package/dist/core/models.d.ts +132 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +303 -0
- package/dist/core/models.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/indexing/embedding-service.d.ts +66 -0
- package/dist/indexing/embedding-service.d.ts.map +1 -0
- package/dist/indexing/embedding-service.js +271 -0
- package/dist/indexing/embedding-service.js.map +1 -0
- package/dist/indexing/memory-retriever.d.ts +58 -0
- package/dist/indexing/memory-retriever.d.ts.map +1 -0
- package/dist/indexing/memory-retriever.js +327 -0
- package/dist/indexing/memory-retriever.js.map +1 -0
- package/dist/indexing/parallel-indexer.d.ts +36 -0
- package/dist/indexing/parallel-indexer.d.ts.map +1 -0
- package/dist/indexing/parallel-indexer.js +67 -0
- package/dist/indexing/parallel-indexer.js.map +1 -0
- package/dist/indexing/source-retriever.d.ts +66 -0
- package/dist/indexing/source-retriever.d.ts.map +1 -0
- package/dist/indexing/source-retriever.js +420 -0
- package/dist/indexing/source-retriever.js.map +1 -0
- package/dist/mcp/server.d.ts +16 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +370 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/state.d.ts +26 -0
- package/dist/mcp/state.d.ts.map +1 -0
- package/dist/mcp/state.js +154 -0
- package/dist/mcp/state.js.map +1 -0
- package/dist/parsers/language-configs.d.ts +26 -0
- package/dist/parsers/language-configs.d.ts.map +1 -0
- package/dist/parsers/language-configs.js +422 -0
- package/dist/parsers/language-configs.js.map +1 -0
- package/dist/parsers/treesitter-parser.d.ts +37 -0
- package/dist/parsers/treesitter-parser.d.ts.map +1 -0
- package/dist/parsers/treesitter-parser.js +602 -0
- package/dist/parsers/treesitter-parser.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding service using @huggingface/transformers (Transformers.js).
|
|
3
|
+
*
|
|
4
|
+
* Runs ONNX models locally in Node.js — no external server needed.
|
|
5
|
+
* Default model: Xenova/all-MiniLM-L6-v2 (384 dims, fast, well-supported)
|
|
6
|
+
*
|
|
7
|
+
* Device Support:
|
|
8
|
+
* Set CODEBAXING_DEVICE environment variable:
|
|
9
|
+
* - 'cpu' (default): CPU inference, works everywhere
|
|
10
|
+
* - 'webgpu': WebGPU backend (experimental, uses Metal on macOS)
|
|
11
|
+
* - 'cuda': NVIDIA GPU (Linux/Windows only, requires CUDA drivers)
|
|
12
|
+
* - 'auto': Auto-detect best available device
|
|
13
|
+
*
|
|
14
|
+
* Note: macOS does not support CUDA. Use 'webgpu' for GPU acceleration on Mac.
|
|
15
|
+
*/
|
|
16
|
+
import { EmbeddingError } from '../core/exceptions.js';
|
|
17
|
+
const VALID_DEVICES = ['cpu', 'cuda', 'webgpu', 'auto'];
|
|
18
|
+
/**
|
|
19
|
+
* Get the configured device from environment variable.
|
|
20
|
+
* Defaults to 'cpu' for maximum compatibility.
|
|
21
|
+
*/
|
|
22
|
+
export function getConfiguredDevice() {
|
|
23
|
+
const envDevice = process.env.CODEBAXING_DEVICE?.toLowerCase();
|
|
24
|
+
if (envDevice && VALID_DEVICES.includes(envDevice)) {
|
|
25
|
+
return envDevice;
|
|
26
|
+
}
|
|
27
|
+
return 'cpu';
|
|
28
|
+
}
|
|
29
|
+
// Lazy import transformers (heavy dependency)
|
|
30
|
+
// Using any types for transformers.js as its types are complex and version-dependent
|
|
31
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
32
|
+
let pipeline;
|
|
33
|
+
let env;
|
|
34
|
+
let transformersLoaded = false;
|
|
35
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
36
|
+
async function importTransformers() {
|
|
37
|
+
if (transformersLoaded)
|
|
38
|
+
return;
|
|
39
|
+
const transformers = await import('@huggingface/transformers');
|
|
40
|
+
pipeline = transformers.pipeline;
|
|
41
|
+
env = transformers.env;
|
|
42
|
+
// Disable remote model downloads warning
|
|
43
|
+
env.allowLocalModels = true;
|
|
44
|
+
env.allowRemoteModels = true;
|
|
45
|
+
transformersLoaded = true;
|
|
46
|
+
}
|
|
47
|
+
export const EMBEDDING_MODELS = {
|
|
48
|
+
'all-MiniLM-L6-v2': {
|
|
49
|
+
modelId: 'Xenova/all-MiniLM-L6-v2',
|
|
50
|
+
dimensions: 384,
|
|
51
|
+
maxSeqLength: 256,
|
|
52
|
+
queryPrefix: '',
|
|
53
|
+
documentPrefix: '',
|
|
54
|
+
},
|
|
55
|
+
'coderankembed': {
|
|
56
|
+
modelId: 'nomic-ai/CodeRankEmbed',
|
|
57
|
+
dimensions: 768,
|
|
58
|
+
maxSeqLength: 8192,
|
|
59
|
+
queryPrefix: 'Represent this query for searching relevant code: ',
|
|
60
|
+
documentPrefix: '',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
export const DEFAULT_MODEL = 'all-MiniLM-L6-v2';
|
|
64
|
+
// ─── EmbeddingService ────────────────────────────────────────────────────────
|
|
65
|
+
export class EmbeddingService {
|
|
66
|
+
modelName;
|
|
67
|
+
config;
|
|
68
|
+
device;
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
extractor = null;
|
|
71
|
+
loading = null;
|
|
72
|
+
showProgress;
|
|
73
|
+
// LRU cache
|
|
74
|
+
cache = new Map();
|
|
75
|
+
cacheMaxSize = 5000;
|
|
76
|
+
// Stats
|
|
77
|
+
stats = {
|
|
78
|
+
totalEmbeddings: 0,
|
|
79
|
+
totalBatches: 0,
|
|
80
|
+
totalTime: 0,
|
|
81
|
+
cacheHits: 0,
|
|
82
|
+
cacheMisses: 0,
|
|
83
|
+
};
|
|
84
|
+
constructor(modelName = DEFAULT_MODEL, options = {}) {
|
|
85
|
+
this.modelName = modelName;
|
|
86
|
+
this.showProgress = options.showProgress ?? false;
|
|
87
|
+
this.device = options.device ?? getConfiguredDevice();
|
|
88
|
+
if (modelName in EMBEDDING_MODELS) {
|
|
89
|
+
this.config = { ...EMBEDDING_MODELS[modelName] };
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Assume it's a HuggingFace model ID
|
|
93
|
+
this.config = {
|
|
94
|
+
modelId: modelName,
|
|
95
|
+
dimensions: 384,
|
|
96
|
+
maxSeqLength: 512,
|
|
97
|
+
queryPrefix: '',
|
|
98
|
+
documentPrefix: '',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Get the device being used for inference */
|
|
103
|
+
getDevice() {
|
|
104
|
+
return this.device;
|
|
105
|
+
}
|
|
106
|
+
get dimensions() {
|
|
107
|
+
return this.config.dimensions;
|
|
108
|
+
}
|
|
109
|
+
async loadModel() {
|
|
110
|
+
if (this.extractor)
|
|
111
|
+
return;
|
|
112
|
+
if (this.loading)
|
|
113
|
+
return this.loading;
|
|
114
|
+
this.loading = (async () => {
|
|
115
|
+
await importTransformers();
|
|
116
|
+
const deviceLabel = this.device === 'cpu' ? 'CPU' : this.device.toUpperCase();
|
|
117
|
+
if (this.showProgress) {
|
|
118
|
+
console.log(`Loading embedding model: ${this.config.modelId} (device: ${deviceLabel})`);
|
|
119
|
+
}
|
|
120
|
+
// Build pipeline options
|
|
121
|
+
const pipelineOptions = {
|
|
122
|
+
quantized: true,
|
|
123
|
+
};
|
|
124
|
+
// Configure device (Transformers.js uses 'device' option)
|
|
125
|
+
// Note: 'auto' lets Transformers.js pick the best available
|
|
126
|
+
if (this.device !== 'cpu') {
|
|
127
|
+
pipelineOptions.device = this.device;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
this.extractor = await pipeline('feature-extraction', this.config.modelId, pipelineOptions);
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
const errorMsg = e.message;
|
|
134
|
+
// If GPU failed, try falling back to CPU
|
|
135
|
+
if (this.device !== 'cpu' && (errorMsg.includes('GPU') || errorMsg.includes('CUDA') || errorMsg.includes('WebGPU'))) {
|
|
136
|
+
console.warn(`Failed to use ${deviceLabel}: ${errorMsg}. Falling back to CPU.`);
|
|
137
|
+
this.device = 'cpu';
|
|
138
|
+
this.extractor = await pipeline('feature-extraction', this.config.modelId, {
|
|
139
|
+
quantized: true,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else if (this.modelName !== DEFAULT_MODEL) {
|
|
143
|
+
// Fall back to default model if custom model fails
|
|
144
|
+
console.warn(`Failed to load model ${this.config.modelId}: ${errorMsg}. ` +
|
|
145
|
+
`Falling back to ${DEFAULT_MODEL}`);
|
|
146
|
+
this.config = { ...EMBEDDING_MODELS[DEFAULT_MODEL] };
|
|
147
|
+
this.extractor = await pipeline('feature-extraction', this.config.modelId, {
|
|
148
|
+
quantized: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
throw new EmbeddingError(`Failed to load embedding model: ${errorMsg}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (this.showProgress) {
|
|
156
|
+
const actualDevice = this.device === 'cpu' ? 'CPU' : this.device.toUpperCase();
|
|
157
|
+
console.log(`Model loaded: ${this.config.modelId} (${this.config.dimensions} dims, ${actualDevice})`);
|
|
158
|
+
}
|
|
159
|
+
this.loading = null;
|
|
160
|
+
})();
|
|
161
|
+
return this.loading;
|
|
162
|
+
}
|
|
163
|
+
async embed(text, isQuery = false) {
|
|
164
|
+
// Check cache
|
|
165
|
+
const cacheKey = `${isQuery ? 'q:' : 'd:'}${text}`;
|
|
166
|
+
const cached = this.cache.get(cacheKey);
|
|
167
|
+
if (cached) {
|
|
168
|
+
this.stats.cacheHits++;
|
|
169
|
+
return cached;
|
|
170
|
+
}
|
|
171
|
+
await this.loadModel();
|
|
172
|
+
const prefix = isQuery ? this.config.queryPrefix : this.config.documentPrefix;
|
|
173
|
+
const inputText = prefix ? prefix + text : text;
|
|
174
|
+
const output = await this.extractor(inputText, {
|
|
175
|
+
pooling: 'mean',
|
|
176
|
+
normalize: true,
|
|
177
|
+
});
|
|
178
|
+
const embedding = Array.from(output.data);
|
|
179
|
+
// Update cache
|
|
180
|
+
this.stats.cacheMisses++;
|
|
181
|
+
this.stats.totalEmbeddings++;
|
|
182
|
+
if (this.cache.size >= this.cacheMaxSize) {
|
|
183
|
+
// Remove oldest entry
|
|
184
|
+
const firstKey = this.cache.keys().next().value;
|
|
185
|
+
this.cache.delete(firstKey);
|
|
186
|
+
}
|
|
187
|
+
this.cache.set(cacheKey, embedding);
|
|
188
|
+
return embedding;
|
|
189
|
+
}
|
|
190
|
+
async embedBatch(texts, isQuery = false) {
|
|
191
|
+
if (texts.length === 0)
|
|
192
|
+
return [];
|
|
193
|
+
await this.loadModel();
|
|
194
|
+
const startTime = performance.now();
|
|
195
|
+
const prefix = isQuery ? this.config.queryPrefix : this.config.documentPrefix;
|
|
196
|
+
const inputTexts = prefix ? texts.map(t => prefix + t) : texts;
|
|
197
|
+
// Process in sub-batches to manage memory
|
|
198
|
+
const batchSize = 32;
|
|
199
|
+
const allEmbeddings = [];
|
|
200
|
+
for (let i = 0; i < inputTexts.length; i += batchSize) {
|
|
201
|
+
const batch = inputTexts.slice(i, i + batchSize);
|
|
202
|
+
// Process each text individually (Transformers.js handles batching internally)
|
|
203
|
+
for (const text of batch) {
|
|
204
|
+
try {
|
|
205
|
+
const output = await this.extractor(text, {
|
|
206
|
+
pooling: 'mean',
|
|
207
|
+
normalize: true,
|
|
208
|
+
});
|
|
209
|
+
allEmbeddings.push(Array.from(output.data));
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
console.warn(`Embedding error for text: ${e.message}. Skipping.`);
|
|
213
|
+
// Push zero vector as fallback
|
|
214
|
+
allEmbeddings.push(new Array(this.config.dimensions).fill(0));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const elapsed = (performance.now() - startTime) / 1000;
|
|
219
|
+
this.stats.totalEmbeddings += texts.length;
|
|
220
|
+
this.stats.totalBatches++;
|
|
221
|
+
this.stats.totalTime += elapsed;
|
|
222
|
+
return allEmbeddings;
|
|
223
|
+
}
|
|
224
|
+
getStats() {
|
|
225
|
+
return {
|
|
226
|
+
...this.stats,
|
|
227
|
+
device: this.device,
|
|
228
|
+
embeddingsPerSecond: this.stats.totalTime > 0
|
|
229
|
+
? this.stats.totalEmbeddings / this.stats.totalTime
|
|
230
|
+
: 0,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
clearCache() {
|
|
234
|
+
this.cache.clear();
|
|
235
|
+
this.stats.cacheHits = 0;
|
|
236
|
+
this.stats.cacheMisses = 0;
|
|
237
|
+
}
|
|
238
|
+
async unload() {
|
|
239
|
+
if (this.extractor) {
|
|
240
|
+
this.extractor = null;
|
|
241
|
+
this.cache.clear();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// ─── Singleton Management ────────────────────────────────────────────────────
|
|
246
|
+
const embeddingServices = new Map();
|
|
247
|
+
export function getEmbeddingService(modelName = DEFAULT_MODEL, options = {}) {
|
|
248
|
+
// Include device in cache key to support different devices per model
|
|
249
|
+
const device = options.device ?? getConfiguredDevice();
|
|
250
|
+
const cacheKey = `${modelName}:${device}`;
|
|
251
|
+
if (!embeddingServices.has(cacheKey)) {
|
|
252
|
+
embeddingServices.set(cacheKey, new EmbeddingService(modelName, { ...options, device }));
|
|
253
|
+
}
|
|
254
|
+
return embeddingServices.get(cacheKey);
|
|
255
|
+
}
|
|
256
|
+
export async function resetEmbeddingService(modelName) {
|
|
257
|
+
if (modelName) {
|
|
258
|
+
const service = embeddingServices.get(modelName);
|
|
259
|
+
if (service) {
|
|
260
|
+
await service.unload();
|
|
261
|
+
embeddingServices.delete(modelName);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
for (const service of embeddingServices.values()) {
|
|
266
|
+
await service.unload();
|
|
267
|
+
}
|
|
268
|
+
embeddingServices.clear();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=embedding-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-service.js","sourceRoot":"","sources":["../../src/indexing/embedding-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory retriever for semantic memory search.
|
|
3
|
+
*
|
|
4
|
+
* Provides semantic search over memories (conversations, status,
|
|
5
|
+
* decisions, etc.) using the same embedding infrastructure as code search.
|
|
6
|
+
*/
|
|
7
|
+
import { EmbeddingService } from './embedding-service.js';
|
|
8
|
+
import { Memory } from '../core/models.js';
|
|
9
|
+
export declare class MemoryRetriever {
|
|
10
|
+
static readonly COLLECTION_NAME = "memories";
|
|
11
|
+
static readonly METADATA_FILE = "memory_metadata.json";
|
|
12
|
+
private projectPath;
|
|
13
|
+
private projectId;
|
|
14
|
+
private embeddingService;
|
|
15
|
+
private chromaClient;
|
|
16
|
+
private collection;
|
|
17
|
+
private persistPath;
|
|
18
|
+
private verbose;
|
|
19
|
+
private initialized;
|
|
20
|
+
stats: Record<string, unknown>;
|
|
21
|
+
constructor(options: {
|
|
22
|
+
projectPath: string;
|
|
23
|
+
embeddingModel?: string;
|
|
24
|
+
verbose?: boolean;
|
|
25
|
+
persistPath?: string;
|
|
26
|
+
embeddingService?: EmbeddingService;
|
|
27
|
+
});
|
|
28
|
+
private ensureInitialized;
|
|
29
|
+
private log;
|
|
30
|
+
private loadStats;
|
|
31
|
+
private saveStats;
|
|
32
|
+
remember(options: {
|
|
33
|
+
content: string;
|
|
34
|
+
memoryType: string;
|
|
35
|
+
tags?: string[];
|
|
36
|
+
ttl?: string;
|
|
37
|
+
source?: string;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
}): Promise<Memory>;
|
|
40
|
+
recall(options: {
|
|
41
|
+
query: string;
|
|
42
|
+
memoryType?: string;
|
|
43
|
+
tags?: string[];
|
|
44
|
+
nResults?: number;
|
|
45
|
+
timeRange?: string;
|
|
46
|
+
minRelevance?: number;
|
|
47
|
+
}): Promise<Record<string, unknown>[]>;
|
|
48
|
+
forget(options: {
|
|
49
|
+
memoryId?: string;
|
|
50
|
+
memoryType?: string;
|
|
51
|
+
tags?: string[];
|
|
52
|
+
olderThan?: string;
|
|
53
|
+
}): Promise<{
|
|
54
|
+
deleted: number;
|
|
55
|
+
}>;
|
|
56
|
+
getStats(): Promise<Record<string, unknown>>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=memory-retriever.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-retriever.d.ts","sourceRoot":"","sources":["../../src/indexing/memory-retriever.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,gBAAgB,EAAuB,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,MAAM,EAA8C,MAAM,mBAAmB,CAAC;AAWvF,qBAAa,eAAe;IAC1B,MAAM,CAAC,QAAQ,CAAC,eAAe,cAAc;IAC7C,MAAM,CAAC,QAAQ,CAAC,aAAa,0BAA0B;IAEvD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,WAAW,CAAS;IAE5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI5B;gBAEU,OAAO,EAAE;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;KACrC;YAuBa,iBAAiB;IAY/B,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,SAAS;IASX,QAAQ,CAAC,OAAO,EAAE;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC,MAAM,CAAC;IA6Cb,MAAM,CAAC,OAAO,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAyGhC,MAAM,CAAC,OAAO,EAAE;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAmF1B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CA6BnD"}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory retriever for semantic memory search.
|
|
3
|
+
*
|
|
4
|
+
* Provides semantic search over memories (conversations, status,
|
|
5
|
+
* decisions, etc.) using the same embedding infrastructure as code search.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import crypto from 'node:crypto';
|
|
10
|
+
import { ChromaClient, IncludeEnum } from 'chromadb';
|
|
11
|
+
import { getEmbeddingService } from './embedding-service.js';
|
|
12
|
+
import { Memory, MemoryType, memoryTypeFromString } from '../core/models.js';
|
|
13
|
+
// TTL duration mappings (in milliseconds)
|
|
14
|
+
const TTL_DURATIONS = {
|
|
15
|
+
session: 24 * 60 * 60 * 1000, // 24 hours
|
|
16
|
+
day: 24 * 60 * 60 * 1000, // 1 day
|
|
17
|
+
week: 7 * 24 * 60 * 60 * 1000, // 1 week
|
|
18
|
+
month: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
19
|
+
permanent: null, // Never expires
|
|
20
|
+
};
|
|
21
|
+
export class MemoryRetriever {
|
|
22
|
+
static COLLECTION_NAME = 'memories';
|
|
23
|
+
static METADATA_FILE = 'memory_metadata.json';
|
|
24
|
+
projectPath;
|
|
25
|
+
projectId;
|
|
26
|
+
embeddingService;
|
|
27
|
+
chromaClient;
|
|
28
|
+
collection;
|
|
29
|
+
persistPath;
|
|
30
|
+
verbose;
|
|
31
|
+
initialized = false;
|
|
32
|
+
stats = {
|
|
33
|
+
totalMemories: 0,
|
|
34
|
+
byType: {},
|
|
35
|
+
lastCleanup: null,
|
|
36
|
+
};
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.projectPath = path.resolve(options.projectPath);
|
|
39
|
+
this.projectId = this.projectPath;
|
|
40
|
+
this.verbose = options.verbose ?? false;
|
|
41
|
+
this.persistPath = options.persistPath;
|
|
42
|
+
this.embeddingService = options.embeddingService ?? getEmbeddingService(options.embeddingModel ?? 'all-MiniLM-L6-v2', { showProgress: false });
|
|
43
|
+
if (this.persistPath) {
|
|
44
|
+
fs.mkdirSync(this.persistPath, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
// ChromaDB Node.js client connects to a server.
|
|
47
|
+
// Set CHROMADB_URL env var to connect to a running ChromaDB server.
|
|
48
|
+
const chromaUrl = process.env.CHROMADB_URL;
|
|
49
|
+
this.chromaClient = chromaUrl
|
|
50
|
+
? new ChromaClient({ path: chromaUrl })
|
|
51
|
+
: new ChromaClient();
|
|
52
|
+
}
|
|
53
|
+
async ensureInitialized() {
|
|
54
|
+
if (this.initialized)
|
|
55
|
+
return;
|
|
56
|
+
this.collection = await this.chromaClient.getOrCreateCollection({
|
|
57
|
+
name: MemoryRetriever.COLLECTION_NAME,
|
|
58
|
+
metadata: { description: 'Memory storage for conversations, status, decisions' },
|
|
59
|
+
});
|
|
60
|
+
this.loadStats();
|
|
61
|
+
this.initialized = true;
|
|
62
|
+
}
|
|
63
|
+
log(message) {
|
|
64
|
+
if (this.verbose)
|
|
65
|
+
console.log(`[Memory] ${message}`);
|
|
66
|
+
}
|
|
67
|
+
loadStats() {
|
|
68
|
+
if (this.persistPath) {
|
|
69
|
+
const metadataPath = path.join(path.dirname(this.persistPath), MemoryRetriever.METADATA_FILE);
|
|
70
|
+
try {
|
|
71
|
+
if (fs.existsSync(metadataPath)) {
|
|
72
|
+
const data = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
|
73
|
+
this.stats = data.stats ?? this.stats;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* ignore */ }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
saveStats() {
|
|
80
|
+
if (this.persistPath) {
|
|
81
|
+
const metadataPath = path.join(path.dirname(this.persistPath), MemoryRetriever.METADATA_FILE);
|
|
82
|
+
try {
|
|
83
|
+
fs.writeFileSync(metadataPath, JSON.stringify({ stats: this.stats }, null, 2));
|
|
84
|
+
}
|
|
85
|
+
catch { /* ignore */ }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async remember(options) {
|
|
89
|
+
await this.ensureInitialized();
|
|
90
|
+
const memory = new Memory({
|
|
91
|
+
id: crypto.randomUUID(),
|
|
92
|
+
content: options.content,
|
|
93
|
+
memoryType: memoryTypeFromString(options.memoryType),
|
|
94
|
+
project: this.projectId,
|
|
95
|
+
tags: options.tags ?? [],
|
|
96
|
+
ttl: options.ttl ?? 'permanent',
|
|
97
|
+
source: options.source ?? 'user',
|
|
98
|
+
metadata: options.metadata ?? {},
|
|
99
|
+
});
|
|
100
|
+
const embedding = await this.embeddingService.embed(options.content);
|
|
101
|
+
// Flatten metadata for ChromaDB
|
|
102
|
+
const chromaMetadata = {
|
|
103
|
+
memory_type: memory.memoryType,
|
|
104
|
+
project: memory.project,
|
|
105
|
+
tags: memory.tags.join(','),
|
|
106
|
+
created_at: memory.createdAt,
|
|
107
|
+
accessed_at: memory.accessedAt,
|
|
108
|
+
ttl: memory.ttl,
|
|
109
|
+
source: memory.source,
|
|
110
|
+
};
|
|
111
|
+
await this.collection.add({
|
|
112
|
+
ids: [memory.id],
|
|
113
|
+
embeddings: [embedding],
|
|
114
|
+
documents: [memory.content],
|
|
115
|
+
metadatas: [chromaMetadata],
|
|
116
|
+
});
|
|
117
|
+
// Update stats
|
|
118
|
+
const count = await this.collection.count();
|
|
119
|
+
this.stats.totalMemories = count;
|
|
120
|
+
const byType = this.stats.byType;
|
|
121
|
+
byType[memory.memoryType] = (byType[memory.memoryType] ?? 0) + 1;
|
|
122
|
+
this.saveStats();
|
|
123
|
+
this.log(`Stored memory: ${memory.id.substring(0, 8)}... (${options.memoryType})`);
|
|
124
|
+
return memory;
|
|
125
|
+
}
|
|
126
|
+
async recall(options) {
|
|
127
|
+
await this.ensureInitialized();
|
|
128
|
+
const count = await this.collection.count();
|
|
129
|
+
if (count === 0)
|
|
130
|
+
return [];
|
|
131
|
+
const nResults = options.nResults ?? 5;
|
|
132
|
+
const queryEmbedding = await this.embeddingService.embed(options.query, true);
|
|
133
|
+
// Parse time range
|
|
134
|
+
let timeCutoff = null;
|
|
135
|
+
if (options.timeRange && options.timeRange !== 'all') {
|
|
136
|
+
const now = new Date();
|
|
137
|
+
const ranges = {
|
|
138
|
+
today: 24 * 60 * 60 * 1000,
|
|
139
|
+
week: 7 * 24 * 60 * 60 * 1000,
|
|
140
|
+
month: 30 * 24 * 60 * 60 * 1000,
|
|
141
|
+
};
|
|
142
|
+
if (options.timeRange in ranges) {
|
|
143
|
+
timeCutoff = new Date(now.getTime() - ranges[options.timeRange]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Build filter
|
|
147
|
+
const whereClauses = [{ project: this.projectId }];
|
|
148
|
+
if (options.memoryType) {
|
|
149
|
+
whereClauses.push({ memory_type: options.memoryType });
|
|
150
|
+
}
|
|
151
|
+
const chromaWhere = whereClauses.length > 1
|
|
152
|
+
? { $and: whereClauses }
|
|
153
|
+
: whereClauses[0];
|
|
154
|
+
try {
|
|
155
|
+
const results = await this.collection.query({
|
|
156
|
+
queryEmbeddings: [queryEmbedding],
|
|
157
|
+
nResults: Math.min(nResults * 2, 50),
|
|
158
|
+
// ChromaDB types don't fully support $and operator typing
|
|
159
|
+
where: chromaWhere,
|
|
160
|
+
include: [IncludeEnum.Documents, IncludeEnum.Metadatas, IncludeEnum.Distances],
|
|
161
|
+
});
|
|
162
|
+
if (!results.ids?.[0]?.length)
|
|
163
|
+
return [];
|
|
164
|
+
const memories = [];
|
|
165
|
+
const ids = results.ids[0];
|
|
166
|
+
const docs = results.documents?.[0] ?? [];
|
|
167
|
+
const metas = results.metadatas?.[0] ?? [];
|
|
168
|
+
const distances = results.distances?.[0] ?? [];
|
|
169
|
+
for (let i = 0; i < ids.length; i++) {
|
|
170
|
+
const distance = distances[i];
|
|
171
|
+
const relevance = 1.0 / (1.0 + distance);
|
|
172
|
+
const minRelevance = options.minRelevance ?? 0;
|
|
173
|
+
if (relevance < minRelevance)
|
|
174
|
+
continue;
|
|
175
|
+
const metadata = metas[i];
|
|
176
|
+
// Time range filter
|
|
177
|
+
if (timeCutoff && metadata.created_at) {
|
|
178
|
+
try {
|
|
179
|
+
const created = new Date(metadata.created_at);
|
|
180
|
+
if (created < timeCutoff)
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
catch { /* skip */ }
|
|
184
|
+
}
|
|
185
|
+
// Tags filter
|
|
186
|
+
if (options.tags?.length) {
|
|
187
|
+
const storedTags = new Set(metadata.tags?.split(',') ?? []);
|
|
188
|
+
if (!options.tags.some(t => storedTags.has(t)))
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
memories.push({
|
|
192
|
+
id: ids[i],
|
|
193
|
+
content: docs[i],
|
|
194
|
+
memoryType: metadata.memory_type,
|
|
195
|
+
tags: metadata.tags ? metadata.tags.split(',').filter(Boolean) : [],
|
|
196
|
+
createdAt: metadata.created_at,
|
|
197
|
+
relevance: Math.round(relevance * 1000) / 1000,
|
|
198
|
+
source: metadata.source ?? 'unknown',
|
|
199
|
+
});
|
|
200
|
+
if (memories.length >= nResults)
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
// Update accessed_at for returned memories
|
|
204
|
+
const now = new Date().toISOString();
|
|
205
|
+
for (const mem of memories) {
|
|
206
|
+
try {
|
|
207
|
+
await this.collection.update({
|
|
208
|
+
ids: [mem.id],
|
|
209
|
+
metadatas: [{ accessed_at: now }],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch { /* non-critical */ }
|
|
213
|
+
}
|
|
214
|
+
this.log(`Recalled ${memories.length} memories for query: ${options.query.substring(0, 50)}...`);
|
|
215
|
+
return memories;
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
this.log(`Query error: ${e.message}`);
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async forget(options) {
|
|
223
|
+
await this.ensureInitialized();
|
|
224
|
+
let deleted = 0;
|
|
225
|
+
if (options.memoryId) {
|
|
226
|
+
try {
|
|
227
|
+
const existing = await this.collection.get({ ids: [options.memoryId] });
|
|
228
|
+
if (existing.ids.length > 0) {
|
|
229
|
+
await this.collection.delete({ ids: [options.memoryId] });
|
|
230
|
+
deleted = 1;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch { /* ignore */ }
|
|
234
|
+
}
|
|
235
|
+
else if (options.olderThan || options.tags?.length) {
|
|
236
|
+
// Fetch all and filter
|
|
237
|
+
const allMemories = await this.collection.get({
|
|
238
|
+
where: { project: this.projectId },
|
|
239
|
+
include: [IncludeEnum.Metadatas],
|
|
240
|
+
});
|
|
241
|
+
const idsToDelete = [];
|
|
242
|
+
const durationMap = {
|
|
243
|
+
'1d': 24 * 60 * 60 * 1000,
|
|
244
|
+
'7d': 7 * 24 * 60 * 60 * 1000,
|
|
245
|
+
'30d': 30 * 24 * 60 * 60 * 1000,
|
|
246
|
+
'1y': 365 * 24 * 60 * 60 * 1000,
|
|
247
|
+
};
|
|
248
|
+
for (let i = 0; i < allMemories.ids.length; i++) {
|
|
249
|
+
const metadata = allMemories.metadatas?.[i];
|
|
250
|
+
let shouldDelete = false;
|
|
251
|
+
if (options.olderThan && options.olderThan in durationMap) {
|
|
252
|
+
const cutoff = Date.now() - durationMap[options.olderThan];
|
|
253
|
+
const created = new Date(metadata.created_at).getTime();
|
|
254
|
+
if (created < cutoff)
|
|
255
|
+
shouldDelete = true;
|
|
256
|
+
}
|
|
257
|
+
if (options.tags?.length) {
|
|
258
|
+
const storedTags = new Set(metadata.tags?.split(',') ?? []);
|
|
259
|
+
if (options.tags.some(t => storedTags.has(t)))
|
|
260
|
+
shouldDelete = true;
|
|
261
|
+
}
|
|
262
|
+
if (options.memoryType && metadata.memory_type !== options.memoryType) {
|
|
263
|
+
shouldDelete = false;
|
|
264
|
+
}
|
|
265
|
+
if (shouldDelete)
|
|
266
|
+
idsToDelete.push(allMemories.ids[i]);
|
|
267
|
+
}
|
|
268
|
+
if (idsToDelete.length > 0) {
|
|
269
|
+
await this.collection.delete({ ids: idsToDelete });
|
|
270
|
+
deleted = idsToDelete.length;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else if (options.memoryType) {
|
|
274
|
+
try {
|
|
275
|
+
const toDelete = await this.collection.get({
|
|
276
|
+
where: {
|
|
277
|
+
$and: [
|
|
278
|
+
{ memory_type: options.memoryType },
|
|
279
|
+
{ project: this.projectId },
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
if (toDelete.ids.length > 0) {
|
|
284
|
+
await this.collection.delete({ ids: toDelete.ids });
|
|
285
|
+
deleted = toDelete.ids.length;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
this.log(`Delete error: ${e.message}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Update stats
|
|
293
|
+
const count = await this.collection.count();
|
|
294
|
+
this.stats.totalMemories = count;
|
|
295
|
+
this.saveStats();
|
|
296
|
+
this.log(`Forgot ${deleted} memories`);
|
|
297
|
+
return { deleted };
|
|
298
|
+
}
|
|
299
|
+
async getStats() {
|
|
300
|
+
await this.ensureInitialized();
|
|
301
|
+
const total = await this.collection.count();
|
|
302
|
+
const byType = {};
|
|
303
|
+
for (const memType of Object.values(MemoryType)) {
|
|
304
|
+
try {
|
|
305
|
+
const result = await this.collection.get({
|
|
306
|
+
where: {
|
|
307
|
+
$and: [
|
|
308
|
+
{ memory_type: memType },
|
|
309
|
+
{ project: this.projectId },
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
if (result.ids.length > 0) {
|
|
314
|
+
byType[memType] = result.ids.length;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch { /* ignore */ }
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
totalMemories: total,
|
|
321
|
+
byType,
|
|
322
|
+
project: this.projectId,
|
|
323
|
+
lastCleanup: this.stats.lastCleanup,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=memory-retriever.js.map
|