@softerist/heuristic-mcp 3.2.3 → 3.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +387 -376
- package/config.jsonc +800 -800
- package/features/ann-config.js +102 -110
- package/features/clear-cache.js +81 -84
- package/features/find-similar-code.js +265 -286
- package/features/hybrid-search.js +487 -536
- package/features/index-codebase.js +3146 -3271
- package/features/lifecycle.js +1011 -1063
- package/features/package-version.js +277 -291
- package/features/register.js +351 -370
- package/features/resources.js +115 -130
- package/features/set-workspace.js +214 -240
- package/index.js +788 -781
- package/lib/cache-ops.js +22 -22
- package/lib/cache-utils.js +465 -519
- package/lib/cache.js +1749 -1849
- package/lib/call-graph.js +396 -396
- package/lib/cli.js +232 -226
- package/lib/config.js +1483 -1495
- package/lib/constants.js +511 -493
- package/lib/embed-query-process.js +206 -212
- package/lib/embedding-process.js +434 -451
- package/lib/embedding-worker.js +862 -934
- package/lib/ignore-patterns.js +276 -316
- package/lib/json-worker.js +14 -14
- package/lib/json-writer.js +302 -310
- package/lib/logging.js +133 -127
- package/lib/memory-logger.js +13 -13
- package/lib/onnx-backend.js +188 -193
- package/lib/path-utils.js +18 -23
- package/lib/project-detector.js +82 -84
- package/lib/server-lifecycle.js +164 -147
- package/lib/settings-editor.js +738 -739
- package/lib/slice-normalize.js +25 -31
- package/lib/tokenizer.js +168 -203
- package/lib/utils.js +364 -409
- package/lib/vector-store-binary.js +973 -991
- package/lib/vector-store-sqlite.js +377 -414
- package/lib/workspace-env.js +32 -34
- package/mcp_config.json +9 -9
- package/package.json +86 -86
- package/scripts/clear-cache.js +20 -20
- package/scripts/download-model.js +43 -43
- package/scripts/mcp-launcher.js +49 -49
- package/scripts/postinstall.js +12 -12
- package/search-configs.js +36 -36
package/lib/embedding-process.js
CHANGED
|
@@ -1,451 +1,434 @@
|
|
|
1
|
-
import { pipeline, env } from '@huggingface/transformers';
|
|
2
|
-
import { configureNativeOnnxBackend } from './onnx-backend.js';
|
|
3
|
-
import {
|
|
4
|
-
EMBEDDING_PROCESS_DEFAULT_GC_MAX_REQUESTS_WITHOUT_COLLECTION,
|
|
5
|
-
EMBEDDING_PROCESS_DEFAULT_GC_MIN_INTERVAL_MS,
|
|
6
|
-
EMBEDDING_PROCESS_DEFAULT_GC_RSS_THRESHOLD_MB,
|
|
7
|
-
EMBEDDING_PROCESS_GC_STATE_INITIAL,
|
|
8
|
-
} from './constants.js';
|
|
9
|
-
import readline from 'readline';
|
|
10
|
-
import path from 'path';
|
|
11
|
-
import os from 'os';
|
|
12
|
-
import { pathToFileURL } from 'url';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
process.stdin.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
process.stdin.on('
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let
|
|
42
|
-
let
|
|
43
|
-
let
|
|
44
|
-
let
|
|
45
|
-
let
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
gcState.lastRunAtMs
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
gcState.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (rssTrigger
|
|
121
|
-
else
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
numThreads,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
gcState.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
.
|
|
404
|
-
.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
function shouldRunMain() {
|
|
436
|
-
if (process.env.EMBEDDING_PROCESS_RUN_MAIN === 'true') return true;
|
|
437
|
-
if (process.env.VITEST) return false;
|
|
438
|
-
if (!process.argv[1]) return false;
|
|
439
|
-
const entryUrl = pathToFileURL(process.argv[1]).href;
|
|
440
|
-
return import.meta.url === entryUrl;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
if (shouldRunMain()) {
|
|
444
|
-
main().catch((err) => {
|
|
445
|
-
log(`[Child:${process.pid}] Error: ${err?.message || err}`);
|
|
446
|
-
process.stderr.write(String(err?.message || err));
|
|
447
|
-
process.exit(1);
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export { getEmbedder, resetEmbeddingProcessState, unloadModel };
|
|
1
|
+
import { pipeline, env } from '@huggingface/transformers';
|
|
2
|
+
import { configureNativeOnnxBackend } from './onnx-backend.js';
|
|
3
|
+
import {
|
|
4
|
+
EMBEDDING_PROCESS_DEFAULT_GC_MAX_REQUESTS_WITHOUT_COLLECTION,
|
|
5
|
+
EMBEDDING_PROCESS_DEFAULT_GC_MIN_INTERVAL_MS,
|
|
6
|
+
EMBEDDING_PROCESS_DEFAULT_GC_RSS_THRESHOLD_MB,
|
|
7
|
+
EMBEDDING_PROCESS_GC_STATE_INITIAL,
|
|
8
|
+
} from './constants.js';
|
|
9
|
+
import readline from 'readline';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import { pathToFileURL } from 'url';
|
|
13
|
+
|
|
14
|
+
let currentRequestId = -1;
|
|
15
|
+
const log = (...args) => {
|
|
16
|
+
if (currentRequestId > 0 && !process.env.EMBEDDING_PROCESS_VERBOSE) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.error(...args);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function formatMemory() {
|
|
23
|
+
const usage = process.memoryUsage();
|
|
24
|
+
return `rss=${(usage.rss / 1024 / 1024).toFixed(1)}MB heap=${(usage.heapUsed / 1024 / 1024).toFixed(1)}MB`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readStdin() {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
let data = '';
|
|
30
|
+
process.stdin.setEncoding('utf8');
|
|
31
|
+
process.stdin.on('data', (chunk) => {
|
|
32
|
+
data += chunk;
|
|
33
|
+
});
|
|
34
|
+
process.stdin.on('end', () => resolve(data));
|
|
35
|
+
process.stdin.on('error', reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const persistent = process.env.EMBEDDING_PROCESS_PERSISTENT === 'true';
|
|
40
|
+
let embedderPromise = null;
|
|
41
|
+
let configuredThreads = null;
|
|
42
|
+
let configuredModel = null;
|
|
43
|
+
let requestCounter = 0;
|
|
44
|
+
let gcSupported = typeof global.gc === 'function';
|
|
45
|
+
let nativeBackendConfigured = false;
|
|
46
|
+
const gcState = { ...EMBEDDING_PROCESS_GC_STATE_INITIAL };
|
|
47
|
+
|
|
48
|
+
function getGlobalCacheDir() {
|
|
49
|
+
if (process.platform === 'win32') {
|
|
50
|
+
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
51
|
+
}
|
|
52
|
+
if (process.platform === 'darwin') {
|
|
53
|
+
return path.join(os.homedir(), 'Library', 'Caches');
|
|
54
|
+
}
|
|
55
|
+
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function toPositiveNumber(value, fallback) {
|
|
59
|
+
const parsed = Number(value);
|
|
60
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toNonNegativeInteger(value, fallback) {
|
|
64
|
+
const parsed = Number.parseInt(value, 10);
|
|
65
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toPositiveInteger(value, fallback) {
|
|
69
|
+
const parsed = Number.parseInt(value, 10);
|
|
70
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveGcPolicy(payload) {
|
|
74
|
+
return {
|
|
75
|
+
rssThresholdMb: toPositiveNumber(
|
|
76
|
+
payload?.gcRssThresholdMb,
|
|
77
|
+
EMBEDDING_PROCESS_DEFAULT_GC_RSS_THRESHOLD_MB
|
|
78
|
+
),
|
|
79
|
+
minIntervalMs: toNonNegativeInteger(
|
|
80
|
+
payload?.gcMinIntervalMs,
|
|
81
|
+
EMBEDDING_PROCESS_DEFAULT_GC_MIN_INTERVAL_MS
|
|
82
|
+
),
|
|
83
|
+
maxRequestsWithoutCollection: toPositiveInteger(
|
|
84
|
+
payload?.gcMaxRequestsWithoutCollection,
|
|
85
|
+
EMBEDDING_PROCESS_DEFAULT_GC_MAX_REQUESTS_WITHOUT_COLLECTION
|
|
86
|
+
),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function maybeRunGc(policy, { reason = 'unknown', force = false } = {}) {
|
|
91
|
+
if (!gcSupported) return false;
|
|
92
|
+
|
|
93
|
+
const before = process.memoryUsage();
|
|
94
|
+
const rssBeforeMb = before.rss / 1024 / 1024;
|
|
95
|
+
const rssTrigger = rssBeforeMb >= policy.rssThresholdMb;
|
|
96
|
+
const requestTrigger = gcState.requestsSinceLastRun >= policy.maxRequestsWithoutCollection;
|
|
97
|
+
|
|
98
|
+
if (!force && !rssTrigger && !requestTrigger) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
if (
|
|
104
|
+
!force &&
|
|
105
|
+
policy.minIntervalMs > 0 &&
|
|
106
|
+
gcState.lastRunAtMs > 0 &&
|
|
107
|
+
now - gcState.lastRunAtMs < policy.minIntervalMs
|
|
108
|
+
) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
global.gc();
|
|
113
|
+
const after = process.memoryUsage();
|
|
114
|
+
gcState.lastRunAtMs = now;
|
|
115
|
+
gcState.requestsSinceLastRun = 0;
|
|
116
|
+
|
|
117
|
+
let trigger = 'forced';
|
|
118
|
+
if (!force) {
|
|
119
|
+
if (rssTrigger && requestTrigger) trigger = 'rss+requests';
|
|
120
|
+
else if (rssTrigger) trigger = 'rss';
|
|
121
|
+
else trigger = 'requests';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
log(
|
|
125
|
+
`[Child:${process.pid}] GC ${reason}: trigger=${trigger} rss ${(before.rss / 1024 / 1024).toFixed(1)}MB -> ${(after.rss / 1024 / 1024).toFixed(1)}MB`
|
|
126
|
+
);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function ensureNativeBackend(threads) {
|
|
131
|
+
if (nativeBackendConfigured && !threads) return;
|
|
132
|
+
configureNativeOnnxBackend({
|
|
133
|
+
log,
|
|
134
|
+
label: '[Child]',
|
|
135
|
+
threads,
|
|
136
|
+
});
|
|
137
|
+
nativeBackendConfigured = true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function setThreads(numThreads) {
|
|
141
|
+
ensureNativeBackend({
|
|
142
|
+
intraOpNumThreads: numThreads,
|
|
143
|
+
interOpNumThreads: 1,
|
|
144
|
+
});
|
|
145
|
+
configuredThreads = numThreads;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function getEmbedder(embeddingModel, numThreads) {
|
|
149
|
+
if (!embedderPromise) {
|
|
150
|
+
configuredModel = embeddingModel;
|
|
151
|
+
setThreads(numThreads);
|
|
152
|
+
env.cacheDir = path.join(getGlobalCacheDir(), 'xenova');
|
|
153
|
+
log(`Loading model ${embeddingModel}...`);
|
|
154
|
+
const loadStart = Date.now();
|
|
155
|
+
embedderPromise = pipeline('feature-extraction', embeddingModel, {
|
|
156
|
+
quantized: true,
|
|
157
|
+
dtype: 'fp32',
|
|
158
|
+
session_options: {
|
|
159
|
+
numThreads,
|
|
160
|
+
intraOpNumThreads: numThreads,
|
|
161
|
+
interOpNumThreads: 1,
|
|
162
|
+
},
|
|
163
|
+
}).then((model) => {
|
|
164
|
+
const loadSec = ((Date.now() - loadStart) / 1000).toFixed(1);
|
|
165
|
+
log(`Model ready in ${loadSec}s, ${formatMemory()}`);
|
|
166
|
+
return model;
|
|
167
|
+
});
|
|
168
|
+
} else if (configuredModel && embeddingModel !== configuredModel) {
|
|
169
|
+
log(`Model changed (${configuredModel} -> ${embeddingModel}); reloading embedder`);
|
|
170
|
+
embedderPromise = null;
|
|
171
|
+
configuredModel = null;
|
|
172
|
+
return getEmbedder(embeddingModel, numThreads);
|
|
173
|
+
} else if (configuredThreads !== null && numThreads !== configuredThreads) {
|
|
174
|
+
log(`Warning: numThreads changed (${configuredThreads} -> ${numThreads})`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return embedderPromise;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function resetEmbeddingProcessState() {
|
|
181
|
+
embedderPromise = null;
|
|
182
|
+
configuredThreads = null;
|
|
183
|
+
configuredModel = null;
|
|
184
|
+
requestCounter = 0;
|
|
185
|
+
currentRequestId = -1;
|
|
186
|
+
nativeBackendConfigured = false;
|
|
187
|
+
gcState.lastRunAtMs = 0;
|
|
188
|
+
gcState.requestsSinceLastRun = 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function unloadModel() {
|
|
192
|
+
if (!embedderPromise) {
|
|
193
|
+
log('[Child] No model loaded, nothing to unload');
|
|
194
|
+
return { success: true, wasLoaded: false };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const embedder = await embedderPromise;
|
|
199
|
+
|
|
200
|
+
if (embedder && typeof embedder.dispose === 'function') {
|
|
201
|
+
try {
|
|
202
|
+
await embedder.dispose();
|
|
203
|
+
log('[Child] Model disposed successfully');
|
|
204
|
+
} catch (disposeErr) {
|
|
205
|
+
log(`[Child] Model dispose warning: ${disposeErr.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
log(`[Child] Error during model unload: ${err.message}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
embedderPromise = null;
|
|
213
|
+
configuredModel = null;
|
|
214
|
+
configuredThreads = null;
|
|
215
|
+
|
|
216
|
+
if (gcSupported) {
|
|
217
|
+
maybeRunGc(resolveGcPolicy(), { reason: 'post-unload', force: true });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
log(`[Child] Model unloaded, ${formatMemory()}`);
|
|
221
|
+
return { success: true, wasLoaded: true };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function runEmbedding(payload) {
|
|
225
|
+
const {
|
|
226
|
+
embeddingModel,
|
|
227
|
+
chunks = [],
|
|
228
|
+
numThreads = 1,
|
|
229
|
+
batchSize = null,
|
|
230
|
+
enableExplicitGc = true,
|
|
231
|
+
gcRssThresholdMb = EMBEDDING_PROCESS_DEFAULT_GC_RSS_THRESHOLD_MB,
|
|
232
|
+
gcMinIntervalMs = EMBEDDING_PROCESS_DEFAULT_GC_MIN_INTERVAL_MS,
|
|
233
|
+
gcMaxRequestsWithoutCollection = EMBEDDING_PROCESS_DEFAULT_GC_MAX_REQUESTS_WITHOUT_COLLECTION,
|
|
234
|
+
requestId = null,
|
|
235
|
+
} = payload || {};
|
|
236
|
+
const shouldRunGc = enableExplicitGc !== false && gcSupported;
|
|
237
|
+
const gcPolicy = resolveGcPolicy({
|
|
238
|
+
gcRssThresholdMb,
|
|
239
|
+
gcMinIntervalMs,
|
|
240
|
+
gcMaxRequestsWithoutCollection,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (!embeddingModel) {
|
|
244
|
+
throw new Error('Missing embeddingModel');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const reqId = requestId ?? requestCounter++;
|
|
248
|
+
currentRequestId = reqId;
|
|
249
|
+
const embedder = await getEmbedder(embeddingModel, numThreads);
|
|
250
|
+
log(`Request ${reqId}: embedding ${chunks.length} chunks, ${formatMemory()}`);
|
|
251
|
+
|
|
252
|
+
const results = [];
|
|
253
|
+
let disposeCount = 0;
|
|
254
|
+
const start = Date.now();
|
|
255
|
+
if (shouldRunGc) {
|
|
256
|
+
gcState.requestsSinceLastRun += 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const BATCH_SIZE = Number.isInteger(batchSize) && batchSize > 0 ? Math.min(batchSize, 256) : 1;
|
|
260
|
+
|
|
261
|
+
for (let batchStart = 0; batchStart < chunks.length; batchStart += BATCH_SIZE) {
|
|
262
|
+
const batchEnd = Math.min(batchStart + BATCH_SIZE, chunks.length);
|
|
263
|
+
const batchChunks = chunks.slice(batchStart, batchEnd);
|
|
264
|
+
const batchTexts = batchChunks.map((c) => c.text);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const output = await embedder(batchTexts, { pooling: 'mean', normalize: true });
|
|
268
|
+
|
|
269
|
+
const hiddenSize = output.dims[output.dims.length - 1];
|
|
270
|
+
|
|
271
|
+
for (let j = 0; j < batchChunks.length; j++) {
|
|
272
|
+
const chunk = batchChunks[j];
|
|
273
|
+
const vecStart = j * hiddenSize;
|
|
274
|
+
const vecEnd = vecStart + hiddenSize;
|
|
275
|
+
|
|
276
|
+
const vector = new Float32Array(output.data.subarray(vecStart, vecEnd));
|
|
277
|
+
|
|
278
|
+
results.push({
|
|
279
|
+
file: chunk.file,
|
|
280
|
+
startLine: chunk.startLine,
|
|
281
|
+
endLine: chunk.endLine,
|
|
282
|
+
content: chunk.text,
|
|
283
|
+
vector: Array.from(vector),
|
|
284
|
+
success: true,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (typeof output.dispose === 'function') {
|
|
289
|
+
try {
|
|
290
|
+
output.dispose();
|
|
291
|
+
} catch {}
|
|
292
|
+
}
|
|
293
|
+
disposeCount++;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
log(`Batch failed, falling back to single: ${error.message}`);
|
|
296
|
+
for (const chunk of batchChunks) {
|
|
297
|
+
try {
|
|
298
|
+
const output = await embedder(chunk.text, { pooling: 'mean', normalize: true });
|
|
299
|
+
const vector = new Float32Array(output.data);
|
|
300
|
+
if (typeof output.dispose === 'function') {
|
|
301
|
+
try {
|
|
302
|
+
output.dispose();
|
|
303
|
+
} catch {}
|
|
304
|
+
}
|
|
305
|
+
disposeCount++;
|
|
306
|
+
results.push({
|
|
307
|
+
file: chunk.file,
|
|
308
|
+
startLine: chunk.startLine,
|
|
309
|
+
endLine: chunk.endLine,
|
|
310
|
+
content: chunk.text,
|
|
311
|
+
vector: Array.from(vector),
|
|
312
|
+
success: true,
|
|
313
|
+
});
|
|
314
|
+
} catch (innerErr) {
|
|
315
|
+
results.push({
|
|
316
|
+
file: chunk.file,
|
|
317
|
+
startLine: chunk.startLine,
|
|
318
|
+
endLine: chunk.endLine,
|
|
319
|
+
error: innerErr.message,
|
|
320
|
+
success: false,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (batchEnd % 20 === 0 || batchEnd === chunks.length) {
|
|
327
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
328
|
+
log(
|
|
329
|
+
`[Child:${process.pid}] Request ${reqId}: processed ${batchEnd}/${chunks.length} chunks in ${elapsed}s, ${formatMemory()}`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (shouldRunGc && (batchEnd % 20 === 0 || batchEnd === chunks.length)) {
|
|
334
|
+
maybeRunGc(gcPolicy, {
|
|
335
|
+
reason: `request ${reqId} progress ${batchEnd}/${chunks.length}`,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const totalSec = ((Date.now() - start) / 1000).toFixed(1);
|
|
341
|
+
log(
|
|
342
|
+
`[Child:${process.pid}] Request ${reqId}: done ${results.length} chunks in ${totalSec}s, ${disposeCount} tensors disposed, ${formatMemory()}`
|
|
343
|
+
);
|
|
344
|
+
if (shouldRunGc) {
|
|
345
|
+
maybeRunGc(gcPolicy, { reason: `request ${reqId} end` });
|
|
346
|
+
}
|
|
347
|
+
const usage = process.memoryUsage();
|
|
348
|
+
return {
|
|
349
|
+
results,
|
|
350
|
+
meta: {
|
|
351
|
+
rssMb: usage.rss / 1024 / 1024,
|
|
352
|
+
heapMb: usage.heapUsed / 1024 / 1024,
|
|
353
|
+
heapTotalMb: usage.heapTotal / 1024 / 1024,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function main() {
|
|
359
|
+
log(`[Child:${process.pid}] Starting, ${formatMemory()}`);
|
|
360
|
+
|
|
361
|
+
if (persistent) {
|
|
362
|
+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
363
|
+
let chain = Promise.resolve();
|
|
364
|
+
|
|
365
|
+
rl.on('line', (line) => {
|
|
366
|
+
const trimmed = line.trim();
|
|
367
|
+
if (!trimmed) return;
|
|
368
|
+
|
|
369
|
+
let payload;
|
|
370
|
+
try {
|
|
371
|
+
payload = JSON.parse(trimmed);
|
|
372
|
+
} catch (err) {
|
|
373
|
+
log(`[Child:${process.pid}] Failed to parse payload: ${err.message}`);
|
|
374
|
+
process.stdout.write(`${JSON.stringify({ results: [] })}\n`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (payload?.type === 'shutdown') {
|
|
379
|
+
rl.close();
|
|
380
|
+
process.exit(0);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (payload?.type === 'unload') {
|
|
385
|
+
chain = chain
|
|
386
|
+
.then(() => unloadModel())
|
|
387
|
+
.then((result) => {
|
|
388
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
389
|
+
})
|
|
390
|
+
.catch((err) => {
|
|
391
|
+
log(`[Child:${process.pid}] Error unloading model: ${err.message}`);
|
|
392
|
+
process.stdout.write(`${JSON.stringify({ success: false, error: err.message })}\n`);
|
|
393
|
+
});
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
chain = chain
|
|
398
|
+
.then(() => runEmbedding(payload))
|
|
399
|
+
.then((output) => {
|
|
400
|
+
process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
401
|
+
})
|
|
402
|
+
.catch((err) => {
|
|
403
|
+
log(`[Child:${process.pid}] Error processing payload: ${err.message}`);
|
|
404
|
+
process.stdout.write(`${JSON.stringify({ results: [] })}\n`);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const raw = await readStdin();
|
|
411
|
+
if (!raw) return;
|
|
412
|
+
|
|
413
|
+
const payload = JSON.parse(raw);
|
|
414
|
+
const output = await runEmbedding(payload);
|
|
415
|
+
process.stdout.write(JSON.stringify(output));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function shouldRunMain() {
|
|
419
|
+
if (process.env.EMBEDDING_PROCESS_RUN_MAIN === 'true') return true;
|
|
420
|
+
if (process.env.VITEST) return false;
|
|
421
|
+
if (!process.argv[1]) return false;
|
|
422
|
+
const entryUrl = pathToFileURL(process.argv[1]).href;
|
|
423
|
+
return import.meta.url === entryUrl;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (shouldRunMain()) {
|
|
427
|
+
main().catch((err) => {
|
|
428
|
+
log(`[Child:${process.pid}] Error: ${err?.message || err}`);
|
|
429
|
+
process.stderr.write(String(err?.message || err));
|
|
430
|
+
process.exit(1);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export { getEmbedder, resetEmbeddingProcessState, unloadModel };
|