flowseeker 0.1.7 → 0.1.9
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/.env.example +7 -0
- package/CHANGELOG.md +143 -108
- package/README.md +288 -221
- package/dist/chat/nativeChatParticipant.js +1 -1
- package/dist/cli/flowCommand.js +175 -0
- package/dist/cli/main.js +1810 -0
- package/dist/cli/mcpServer.js +7 -1
- package/dist/cli/runEvaluation.js +281 -2
- package/dist/config/defaultConfig.js +11 -1
- package/dist/config/env.js +118 -0
- package/dist/config/loadConfig.js +18 -1
- package/dist/config/loadConfigFromPath.js +3 -1
- package/dist/eval/accuracyV2.js +483 -0
- package/dist/eval/goldenTask.js +192 -0
- package/dist/extension.js +23 -0
- package/dist/framework/laravel.js +177 -0
- package/dist/gateway/embeddingProviders.js +852 -0
- package/dist/index/cacheStore.js +43 -0
- package/dist/index/configRouteDiscoveryProbe.js +288 -0
- package/dist/index/embeddingIndex.js +193 -0
- package/dist/index/graphIndex.js +460 -0
- package/dist/index/indexWatcher.js +86 -0
- package/dist/index/semanticChunkIndex.js +388 -0
- package/dist/index/structuredExtractor.js +303 -12
- package/dist/index/treeSitterExtractor.js +264 -0
- package/dist/index/vectorStore.js +41 -0
- package/dist/index/workspaceIndex.js +901 -26
- package/dist/mcp/mcpTools.js +1678 -2
- package/dist/pipeline/contextBlueprint.js +15 -2
- package/dist/pipeline/contextPack.js +3 -3
- package/dist/pipeline/deterministicReranker.js +358 -0
- package/dist/pipeline/evaluationMetrics.js +14 -2
- package/dist/pipeline/fileGroups.js +7 -1
- package/dist/pipeline/fileScanner.js +209 -12
- package/dist/pipeline/fusionTrace.js +149 -0
- package/dist/pipeline/llmReranker.js +151 -0
- package/dist/pipeline/nodeScan.js +102 -12
- package/dist/pipeline/ranker.js +875 -16
- package/dist/pipeline/retrievalFusion.js +41 -0
- package/dist/pipeline/roleRefinement.js +62 -0
- package/dist/pipeline/runHeadless.js +60 -5
- package/dist/pipeline/runPipeline.js +2 -2
- package/dist/pipeline/solvePacket.js +656 -43
- package/dist/pipeline/subsystem.js +21 -0
- package/dist/pipeline/taskUnderstanding.js +2 -2
- package/dist/ui/chatViewProvider.js +1 -1
- package/docs/demo-screenshot-checklist.md +86 -0
- package/docs/marketplace-copy.md +92 -0
- package/docs/mcp-onboarding.md +191 -0
- package/package.json +633 -561
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.localProviderNextAction = localProviderNextAction;
|
|
37
|
+
exports.getLocalProviderReadiness = getLocalProviderReadiness;
|
|
38
|
+
exports.checkLocalProviderReadiness = checkLocalProviderReadiness;
|
|
39
|
+
exports.detectLocalRuntime = detectLocalRuntime;
|
|
40
|
+
exports.detectBundledLocalPackage = detectBundledLocalPackage;
|
|
41
|
+
exports.normalizeEmbeddings = normalizeEmbeddings;
|
|
42
|
+
exports.createOpenAICompatibleEmbeddingProvider = createOpenAICompatibleEmbeddingProvider;
|
|
43
|
+
exports.createConfiguredEmbeddingProvider = createConfiguredEmbeddingProvider;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const module_1 = require("module");
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const env_1 = require("../config/env");
|
|
48
|
+
function localProviderNextAction(status) {
|
|
49
|
+
if (status === "local_unavailable_runtime_missing")
|
|
50
|
+
return "Run flowseeker semantic local setup to install the local embedding runtime in this project. Or, for the ready local path: npm i -g flowseeker flowseeker-local.";
|
|
51
|
+
if (status === "local_unavailable_model_missing")
|
|
52
|
+
return "Run flowseeker semantic local download to download the local embedding model.";
|
|
53
|
+
if (status === "local_unavailable_model_invalid")
|
|
54
|
+
return "Re-download the local embedding model or check semanticLocalModelPath in .flowseeker/config.json.";
|
|
55
|
+
if (status === "bundled_package_missing")
|
|
56
|
+
return "Install the optional local model package: npm i -g flowseeker flowseeker-local. Deterministic retrieval remains available by default.";
|
|
57
|
+
if (status === "bundled_package_invalid")
|
|
58
|
+
return "The flowseeker-local package is installed but its manifest is invalid. Reinstall or check the package version.";
|
|
59
|
+
if (status === "bundled_package_ready")
|
|
60
|
+
return "Built-in local model package is ready. Run flowseeker semantic check --test to verify local embeddings.";
|
|
61
|
+
return "Local embedding provider is ready. Run flowseeker semantic check --test to verify local embeddings.";
|
|
62
|
+
}
|
|
63
|
+
function getLocalProviderReadiness(workspacePath, config) {
|
|
64
|
+
// Check bundled local package first (GitNexus-style)
|
|
65
|
+
const bundled = detectBundledLocalPackage(workspacePath);
|
|
66
|
+
if (bundled.available) {
|
|
67
|
+
if (bundled.valid) {
|
|
68
|
+
return {
|
|
69
|
+
runtimeAvailable: true,
|
|
70
|
+
localStatus: "bundled_package_ready",
|
|
71
|
+
runtimeStatus: "available (bundled package)",
|
|
72
|
+
modelStatus: bundled.modelId || "ready",
|
|
73
|
+
nextAction: localProviderNextAction("bundled_package_ready"),
|
|
74
|
+
reasonWhenSkipped: ""
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
runtimeAvailable: true,
|
|
79
|
+
localStatus: bundled.status,
|
|
80
|
+
runtimeStatus: "available (bundled package)",
|
|
81
|
+
modelStatus: bundled.reason || "invalid",
|
|
82
|
+
nextAction: localProviderNextAction(bundled.status),
|
|
83
|
+
reasonWhenSkipped: bundled.reason || ""
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// Fall through to project-level detection (Phase 11 path)
|
|
87
|
+
const runtimeAvailable = detectLocalRuntime(workspacePath);
|
|
88
|
+
if (!runtimeAvailable)
|
|
89
|
+
return makeReadiness(false, "local_unavailable_runtime_missing", "unavailable (not installed)");
|
|
90
|
+
if (!config.semanticLocalModelPath)
|
|
91
|
+
return makeReadiness(true, "local_unavailable_model_missing", "not downloaded");
|
|
92
|
+
if (config.semanticDimensions !== undefined && config.semanticDimensions <= 0)
|
|
93
|
+
return makeReadiness(true, "local_unavailable_model_invalid", "invalid dimensions");
|
|
94
|
+
const mr = validateLocalModelManifest(config.semanticLocalModelPath);
|
|
95
|
+
return makeReadiness(true, mr.status, mr.detail);
|
|
96
|
+
}
|
|
97
|
+
function makeReadiness(runtimeAvailable, status, modelStatus) {
|
|
98
|
+
return {
|
|
99
|
+
runtimeAvailable, localStatus: status,
|
|
100
|
+
runtimeStatus: runtimeAvailable ? "available" : "unavailable (not installed)",
|
|
101
|
+
modelStatus,
|
|
102
|
+
nextAction: localProviderNextAction(status),
|
|
103
|
+
reasonWhenSkipped: status !== "local_ready" ? "Local runtime/model not available." : ""
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function validateLocalModelManifest(modelDir) {
|
|
107
|
+
const manifestPath = path.join(modelDir, "manifest.json");
|
|
108
|
+
if (!fs.existsSync(manifestPath))
|
|
109
|
+
return { status: "local_unavailable_model_missing", detail: "manifest not found" };
|
|
110
|
+
let raw;
|
|
111
|
+
try {
|
|
112
|
+
raw = fs.readFileSync(manifestPath, "utf8");
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return { status: "local_unavailable_model_missing", detail: "cannot read manifest" };
|
|
116
|
+
}
|
|
117
|
+
let manifest;
|
|
118
|
+
try {
|
|
119
|
+
manifest = JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return { status: "local_unavailable_model_invalid", detail: "manifest is not valid JSON" };
|
|
123
|
+
}
|
|
124
|
+
const manifestStatus = String(manifest.status || "");
|
|
125
|
+
if (manifestStatus === "declared" || manifestStatus === "pending" || !manifestStatus)
|
|
126
|
+
return { status: "local_unavailable_model_missing", detail: "manifest status: " + (manifestStatus || "empty") };
|
|
127
|
+
if (manifestStatus !== "ready" && manifestStatus !== "downloaded")
|
|
128
|
+
return { status: "local_unavailable_model_invalid", detail: "unknown manifest status: " + manifestStatus };
|
|
129
|
+
const dims = Number(manifest.dimensions);
|
|
130
|
+
if (isNaN(dims) || dims <= 0)
|
|
131
|
+
return { status: "local_unavailable_model_invalid", detail: "invalid dimensions in manifest" };
|
|
132
|
+
const artifacts = manifest.artifacts;
|
|
133
|
+
if (!artifacts || typeof artifacts !== "object" || Object.keys(artifacts).length === 0)
|
|
134
|
+
return { status: "local_unavailable_model_missing", detail: "no artifacts in manifest" };
|
|
135
|
+
const resolvedBase = path.resolve(modelDir);
|
|
136
|
+
for (const [key, artifactPath] of Object.entries(artifacts)) {
|
|
137
|
+
if (typeof artifactPath !== "string" || !artifactPath)
|
|
138
|
+
return { status: "local_unavailable_model_invalid", detail: "artifact '" + key + "' has empty or non-string path" };
|
|
139
|
+
if (artifactPath.includes("..") || path.isAbsolute(artifactPath))
|
|
140
|
+
return { status: "local_unavailable_model_invalid", detail: "artifact '" + key + "' path traversal detected" };
|
|
141
|
+
const resolved = path.resolve(path.join(resolvedBase, artifactPath));
|
|
142
|
+
if (!resolved.startsWith(resolvedBase + path.sep) && resolved !== resolvedBase)
|
|
143
|
+
return { status: "local_unavailable_model_invalid", detail: "artifact '" + key + "' escapes model directory" };
|
|
144
|
+
try {
|
|
145
|
+
fs.accessSync(resolved);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return { status: "local_unavailable_model_missing", detail: "artifact '" + key + "' file missing: " + artifactPath };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { status: "local_ready", detail: "ready" };
|
|
152
|
+
}
|
|
153
|
+
function checkLocalProviderReadiness(config, options) {
|
|
154
|
+
// Check runtime
|
|
155
|
+
if (!options?.runtimeAvailable)
|
|
156
|
+
return "local_unavailable_runtime_missing";
|
|
157
|
+
// Check model path
|
|
158
|
+
if (!config.semanticLocalModelPath)
|
|
159
|
+
return "local_unavailable_model_missing";
|
|
160
|
+
// Check model validity (dimensions)
|
|
161
|
+
if (config.semanticDimensions !== undefined && config.semanticDimensions <= 0)
|
|
162
|
+
return "local_unavailable_model_invalid";
|
|
163
|
+
return "local_ready";
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Detect @huggingface/transformers in the target workspace.
|
|
167
|
+
* Prefers Node package resolution from the workspace package.json.
|
|
168
|
+
* Does not import the runtime — only checks presence.
|
|
169
|
+
*/
|
|
170
|
+
function detectLocalRuntime(workspacePath) {
|
|
171
|
+
try {
|
|
172
|
+
const workspaceRequire = (0, module_1.createRequire)(path.join(workspacePath, "package.json"));
|
|
173
|
+
workspaceRequire.resolve("@huggingface/transformers");
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
try {
|
|
178
|
+
fs.accessSync(path.join(workspacePath, "node_modules", "@huggingface", "transformers", "package.json"));
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function getFlowseekerPackageDir() {
|
|
187
|
+
try {
|
|
188
|
+
// __dirname at runtime is dist/gateway/, walk up to find package root
|
|
189
|
+
let dir = path.resolve(__dirname);
|
|
190
|
+
for (let i = 0; i < 10; i++) {
|
|
191
|
+
const candidate = path.join(dir, "package.json");
|
|
192
|
+
if (fs.existsSync(candidate)) {
|
|
193
|
+
try {
|
|
194
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
195
|
+
if (pkg.name === "flowseeker")
|
|
196
|
+
return dir;
|
|
197
|
+
}
|
|
198
|
+
catch { /* keep walking */ }
|
|
199
|
+
}
|
|
200
|
+
const parent = path.dirname(dir);
|
|
201
|
+
if (parent === dir)
|
|
202
|
+
break;
|
|
203
|
+
dir = parent;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch { /* best effort */ }
|
|
207
|
+
}
|
|
208
|
+
function getGlobalModulesDir() {
|
|
209
|
+
try {
|
|
210
|
+
// npm root -g equivalent detection
|
|
211
|
+
const { execSync } = require("child_process");
|
|
212
|
+
const out = execSync("npm root -g", { encoding: "utf8", timeout: 5000, windowsHide: true }).trim();
|
|
213
|
+
if (out && fs.existsSync(out))
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
catch { /* npm not available or failed */ }
|
|
217
|
+
// Fallback: common global paths
|
|
218
|
+
const candidates = [
|
|
219
|
+
process.env.APPDATA ? path.join(process.env.APPDATA, "npm", "node_modules") : undefined,
|
|
220
|
+
"/usr/local/lib/node_modules",
|
|
221
|
+
"/usr/lib/node_modules",
|
|
222
|
+
];
|
|
223
|
+
for (const c of candidates) {
|
|
224
|
+
if (c && fs.existsSync(c))
|
|
225
|
+
return c;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function isReadyStatus(status) {
|
|
229
|
+
return status === "local_ready" || status === "bundled_package_ready";
|
|
230
|
+
}
|
|
231
|
+
function detectBundledLocalPackage(workspacePath) {
|
|
232
|
+
// Explicit override for unusual package managers or custom installs
|
|
233
|
+
const overridePath = process.env.FLOWSEEKER_LOCAL_PACKAGE_PATH;
|
|
234
|
+
if (overridePath && fs.existsSync(path.join(overridePath, "package.json"))) {
|
|
235
|
+
const pkgDir = overridePath;
|
|
236
|
+
return resolveBundledPackage(pkgDir);
|
|
237
|
+
}
|
|
238
|
+
const searchPaths = [
|
|
239
|
+
workspacePath || process.cwd(),
|
|
240
|
+
process.cwd(),
|
|
241
|
+
];
|
|
242
|
+
// Also search workspace node_modules (works even without workspace package.json)
|
|
243
|
+
if (workspacePath) {
|
|
244
|
+
searchPaths.push(path.join(workspacePath, "node_modules"));
|
|
245
|
+
}
|
|
246
|
+
searchPaths.push(path.join(process.cwd(), "node_modules"));
|
|
247
|
+
// Also search from flowseeker's own install location (sibling/global-like path)
|
|
248
|
+
const flowseekerDir = getFlowseekerPackageDir();
|
|
249
|
+
if (flowseekerDir) {
|
|
250
|
+
// flowseekerDir is e.g. /usr/lib/node_modules/flowseeker
|
|
251
|
+
// The sibling would be /usr/lib/node_modules/flowseeker-local
|
|
252
|
+
const parentNodeModules = path.dirname(flowseekerDir);
|
|
253
|
+
searchPaths.push(parentNodeModules);
|
|
254
|
+
}
|
|
255
|
+
// Also try global npm prefix
|
|
256
|
+
const globalDir = getGlobalModulesDir();
|
|
257
|
+
if (globalDir)
|
|
258
|
+
searchPaths.push(globalDir);
|
|
259
|
+
// Extra search paths for unusual setups / smoke testing (semicolon-separated)
|
|
260
|
+
const extraPaths = process.env.FLOWSEEKER_LOCAL_SEARCH_PATHS;
|
|
261
|
+
if (extraPaths) {
|
|
262
|
+
searchPaths.push(...extraPaths.split(";").map(s => s.trim()).filter(Boolean));
|
|
263
|
+
}
|
|
264
|
+
let resolvedPath;
|
|
265
|
+
for (const base of searchPaths) {
|
|
266
|
+
try {
|
|
267
|
+
const pkgJsonPath = path.join(base, "package.json");
|
|
268
|
+
// For pure directory paths (like global node_modules), check filesystem first
|
|
269
|
+
if (fs.existsSync(path.join(base, "flowseeker-local", "package.json"))) {
|
|
270
|
+
resolvedPath = path.join(base, "flowseeker-local", "package.json");
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
// Then try require.resolve if package.json exists at base
|
|
274
|
+
let reqPath = pkgJsonPath;
|
|
275
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
276
|
+
// Try to resolve from a dummy package.json in the base
|
|
277
|
+
reqPath = path.join(base, "flowseeker-local", "package.json");
|
|
278
|
+
if (!fs.existsSync(reqPath))
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const req = (0, module_1.createRequire)(reqPath);
|
|
282
|
+
resolvedPath = req.resolve("flowseeker-local");
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
catch { /* not found in this path */ }
|
|
286
|
+
}
|
|
287
|
+
if (!resolvedPath)
|
|
288
|
+
return { available: false, valid: false, status: "bundled_package_missing", reason: "flowseeker-local package not installed. Run: npm i -g flowseeker flowseeker-local" };
|
|
289
|
+
// Resolve to package root directory
|
|
290
|
+
let pkgDir = resolvedPath;
|
|
291
|
+
// Walk up from resolved module to find package.json of flowseeker-local
|
|
292
|
+
for (let i = 0; i < 10; i++) {
|
|
293
|
+
const candidate = path.join(pkgDir, "package.json");
|
|
294
|
+
if (fs.existsSync(candidate)) {
|
|
295
|
+
try {
|
|
296
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
297
|
+
if (pkg.name === "flowseeker-local") {
|
|
298
|
+
pkgDir = path.dirname(candidate);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch { /* keep walking */ }
|
|
303
|
+
}
|
|
304
|
+
const parent = path.dirname(pkgDir);
|
|
305
|
+
if (parent === pkgDir)
|
|
306
|
+
break;
|
|
307
|
+
pkgDir = parent;
|
|
308
|
+
}
|
|
309
|
+
return resolveBundledPackage(pkgDir);
|
|
310
|
+
}
|
|
311
|
+
function resolveBundledPackage(pkgDir) {
|
|
312
|
+
// Look for a manifest file
|
|
313
|
+
const manifestCandidates = ["model-manifest.json", "manifest.json", ".flowseeker/manifest.json"];
|
|
314
|
+
let manifestPath;
|
|
315
|
+
for (const mc of manifestCandidates) {
|
|
316
|
+
const mp = path.join(pkgDir, mc);
|
|
317
|
+
if (fs.existsSync(mp)) {
|
|
318
|
+
manifestPath = mp;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!manifestPath)
|
|
323
|
+
return { available: true, valid: false, status: "bundled_package_invalid", packagePath: pkgDir, reason: "flowseeker-local package found but no model manifest found." };
|
|
324
|
+
return validatePackageManifest(manifestPath, pkgDir);
|
|
325
|
+
}
|
|
326
|
+
function validatePackageManifest(manifestPath, packageDir) {
|
|
327
|
+
let manifest;
|
|
328
|
+
try {
|
|
329
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return { available: true, valid: false, status: "bundled_package_invalid", manifestPath, packagePath: packageDir, reason: "Manifest is not valid JSON." };
|
|
333
|
+
}
|
|
334
|
+
const status = String(manifest.status || "");
|
|
335
|
+
if (status !== "ready" && status !== "downloaded") {
|
|
336
|
+
return { available: true, valid: false, status: "bundled_package_invalid", manifestPath, packagePath: packageDir, reason: "Manifest status is not ready/downloaded: " + (status || "empty") };
|
|
337
|
+
}
|
|
338
|
+
const dims = Number(manifest.dimensions);
|
|
339
|
+
if (isNaN(dims) || dims <= 0) {
|
|
340
|
+
return { available: true, valid: false, status: "bundled_package_invalid", manifestPath, packagePath: packageDir, reason: "Invalid dimensions in manifest." };
|
|
341
|
+
}
|
|
342
|
+
const artifacts = manifest.artifacts;
|
|
343
|
+
if (!artifacts || typeof artifacts !== "object" || Object.keys(artifacts).length === 0) {
|
|
344
|
+
return { available: true, valid: false, status: "bundled_package_invalid", manifestPath, packagePath: packageDir, reason: "No artifacts in manifest." };
|
|
345
|
+
}
|
|
346
|
+
const modelBase = path.dirname(manifestPath);
|
|
347
|
+
for (const [key, artifactPath] of Object.entries(artifacts)) {
|
|
348
|
+
if (typeof artifactPath !== "string" || !artifactPath) {
|
|
349
|
+
return { available: true, valid: false, status: "bundled_package_invalid", manifestPath, packagePath: packageDir, reason: "Artifact '" + key + "' has empty or non-string path." };
|
|
350
|
+
}
|
|
351
|
+
if (artifactPath.includes("..") || path.isAbsolute(artifactPath)) {
|
|
352
|
+
return { available: true, valid: false, status: "bundled_package_invalid", manifestPath, packagePath: packageDir, reason: "Artifact '" + key + "' path traversal detected." };
|
|
353
|
+
}
|
|
354
|
+
const resolved = path.resolve(path.join(modelBase, artifactPath));
|
|
355
|
+
if (!resolved.startsWith(path.resolve(modelBase) + path.sep) && resolved !== path.resolve(modelBase)) {
|
|
356
|
+
return { available: true, valid: false, status: "bundled_package_invalid", manifestPath, packagePath: packageDir, reason: "Artifact '" + key + "' escapes model directory." };
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
fs.accessSync(resolved);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return { available: true, valid: false, status: "bundled_package_invalid", manifestPath, packagePath: packageDir, reason: "Artifact '" + key + "' file missing: " + artifactPath };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const modelId = String(manifest.modelId || manifest.modelSlug || "");
|
|
366
|
+
return {
|
|
367
|
+
available: true,
|
|
368
|
+
valid: true,
|
|
369
|
+
status: "bundled_package_ready",
|
|
370
|
+
modelId,
|
|
371
|
+
dimensions: dims,
|
|
372
|
+
packagePath: packageDir,
|
|
373
|
+
manifestPath
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function deployBundledModel(packageModelDir, packageRoot) {
|
|
377
|
+
const srcDir = path.join(packageModelDir, "models");
|
|
378
|
+
if (!fs.existsSync(srcDir))
|
|
379
|
+
return;
|
|
380
|
+
// Find @huggingface/transformers package directory
|
|
381
|
+
let tfDir = "";
|
|
382
|
+
try {
|
|
383
|
+
const req = (0, module_1.createRequire)(path.join(packageRoot, "package.json"));
|
|
384
|
+
const tfMain = req.resolve("@huggingface/transformers");
|
|
385
|
+
let d = path.dirname(tfMain);
|
|
386
|
+
for (let i = 0; i < 10; i++) {
|
|
387
|
+
if (fs.existsSync(path.join(d, "package.json"))) {
|
|
388
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(d, "package.json"), "utf8"));
|
|
389
|
+
if (pkg.name === "@huggingface/transformers") {
|
|
390
|
+
tfDir = d;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const parent = path.dirname(d);
|
|
395
|
+
if (parent === d)
|
|
396
|
+
break;
|
|
397
|
+
d = parent;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (!tfDir)
|
|
404
|
+
return;
|
|
405
|
+
const dstDir = path.join(tfDir, "models");
|
|
406
|
+
// Only copy if not already present
|
|
407
|
+
if (!fs.existsSync(path.join(dstDir, "sentence-transformers"))) {
|
|
408
|
+
copyDirSync(srcDir, dstDir);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function copyDirSync(src, dst) {
|
|
412
|
+
if (!fs.existsSync(src))
|
|
413
|
+
return;
|
|
414
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
415
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
416
|
+
for (const entry of entries) {
|
|
417
|
+
const srcPath = path.join(src, entry.name);
|
|
418
|
+
const dstPath = path.join(dst, entry.name);
|
|
419
|
+
if (entry.isDirectory()) {
|
|
420
|
+
copyDirSync(srcPath, dstPath);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
fs.copyFileSync(srcPath, dstPath);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function createLocalEmbeddingProvider(workspacePath, config) {
|
|
428
|
+
const runtimeRoot = config.packageRoot || workspacePath;
|
|
429
|
+
return {
|
|
430
|
+
name: "local",
|
|
431
|
+
model: config.modelId,
|
|
432
|
+
dimensions: config.dimensions,
|
|
433
|
+
async embedTexts(texts) {
|
|
434
|
+
const workspaceRequire = (0, module_1.createRequire)(path.join(runtimeRoot, "package.json"));
|
|
435
|
+
let transformers;
|
|
436
|
+
try {
|
|
437
|
+
transformers = workspaceRequire("@huggingface/transformers");
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
// Fallback: try scaffold-runtime.js in the package root (self-contained fake runtime)
|
|
441
|
+
if (config.packageRoot) {
|
|
442
|
+
const scaffoldPath = path.join(config.packageRoot, "scaffold-runtime.js");
|
|
443
|
+
if (fs.existsSync(scaffoldPath)) {
|
|
444
|
+
transformers = require(scaffoldPath);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (!transformers) {
|
|
448
|
+
throw new Error("Local embedding runtime (@huggingface/transformers) is not installed in this project. Run flowseeker semantic local setup.");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const pipeline = transformers.pipeline || transformers.default?.pipeline;
|
|
452
|
+
if (typeof pipeline !== "function") {
|
|
453
|
+
throw new Error("Local runtime does not export pipeline. Check @huggingface/transformers installation in your project.");
|
|
454
|
+
}
|
|
455
|
+
// For bundled packages, deploy model files to Transformers.js expected location at runtime
|
|
456
|
+
if (config.packageRoot) {
|
|
457
|
+
deployBundledModel(config.modelPath, runtimeRoot);
|
|
458
|
+
}
|
|
459
|
+
const extractor = await pipeline("feature-extraction", config.modelId, {
|
|
460
|
+
local_files_only: true,
|
|
461
|
+
cache_dir: config.modelPath,
|
|
462
|
+
});
|
|
463
|
+
let raw;
|
|
464
|
+
try {
|
|
465
|
+
// Transformers.js v2 returns {__call__: fn}, v3 returns callable object, v4: callable with kwargs
|
|
466
|
+
const callFn = typeof extractor === "function" ? extractor : extractor?.__call__;
|
|
467
|
+
if (typeof callFn !== "function")
|
|
468
|
+
throw new Error("extractor is not callable");
|
|
469
|
+
raw = await callFn(texts.length === 1 ? texts[0] : texts, { pooling: "mean", normalize: true });
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
throw new Error("Local embedding extraction failed: " + (e instanceof Error ? e.message : String(e)));
|
|
473
|
+
}
|
|
474
|
+
return normalizeEmbeddings(raw, texts.length, config.dimensions);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function normalizeEmbeddings(raw, expectedCount, expectedDims) {
|
|
479
|
+
// Extract a flat array from raw tensor-like output
|
|
480
|
+
let flatVec = null;
|
|
481
|
+
if (raw?.data) {
|
|
482
|
+
flatVec = Array.from(raw.data);
|
|
483
|
+
}
|
|
484
|
+
else if (typeof raw?.tolist === "function") {
|
|
485
|
+
flatVec = raw.tolist();
|
|
486
|
+
}
|
|
487
|
+
else if (Array.isArray(raw) && raw.length > 0 && typeof raw[0] === "number") {
|
|
488
|
+
flatVec = raw;
|
|
489
|
+
}
|
|
490
|
+
// If we got a single flat vector that matches expected dimensions,
|
|
491
|
+
// return it directly (or reshape if batch)
|
|
492
|
+
if (flatVec && flatVec.length >= expectedDims) {
|
|
493
|
+
if (flatVec.length === expectedDims) {
|
|
494
|
+
if (expectedCount > 1)
|
|
495
|
+
throw new Error(`Local embedding output count mismatch: expected ${expectedCount}, got 1`);
|
|
496
|
+
if (!flatVec.every((x) => typeof x === "number" && isFinite(x))) {
|
|
497
|
+
throw new Error("Local embedding returned non-numeric or infinite values");
|
|
498
|
+
}
|
|
499
|
+
return [flatVec];
|
|
500
|
+
}
|
|
501
|
+
if (flatVec.length === expectedCount * expectedDims && expectedCount > 1) {
|
|
502
|
+
const vectors = [];
|
|
503
|
+
for (let i = 0; i < expectedCount; i++) {
|
|
504
|
+
const vec = flatVec.slice(i * expectedDims, (i + 1) * expectedDims);
|
|
505
|
+
if (!vec.every((x) => typeof x === "number" && isFinite(x))) {
|
|
506
|
+
throw new Error("Local embedding returned non-numeric or infinite values");
|
|
507
|
+
}
|
|
508
|
+
vectors.push(vec);
|
|
509
|
+
}
|
|
510
|
+
return vectors;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// Unwrap top-level arrays
|
|
514
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
515
|
+
const vectors = [];
|
|
516
|
+
for (const item of items) {
|
|
517
|
+
let vec;
|
|
518
|
+
if (Array.isArray(item)) {
|
|
519
|
+
vec = item;
|
|
520
|
+
}
|
|
521
|
+
else if (item?.data) {
|
|
522
|
+
// Tensor-like: { data: Float32Array | number[] }
|
|
523
|
+
vec = Array.from(item.data);
|
|
524
|
+
}
|
|
525
|
+
else if (typeof item?.tolist === "function") {
|
|
526
|
+
vec = item.tolist();
|
|
527
|
+
}
|
|
528
|
+
else if (typeof item === "object" && item !== null) {
|
|
529
|
+
// Try numeric object keys
|
|
530
|
+
const keys = Object.keys(item).map(Number).filter(k => !isNaN(k)).sort((a, b) => a - b);
|
|
531
|
+
if (keys.length > 0 && keys[0] === 0 && keys[keys.length - 1] === keys.length - 1) {
|
|
532
|
+
vec = keys.map(k => Number(item[k]));
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
throw new Error("Local embedding output has unrecognized format: " + JSON.stringify(item).slice(0, 200));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
throw new Error("Local embedding output has unrecognized format: " + typeof item);
|
|
540
|
+
}
|
|
541
|
+
if (vec.length === 0)
|
|
542
|
+
throw new Error("Local embedding returned empty vector");
|
|
543
|
+
if (!vec.every((x) => typeof x === "number" && isFinite(x))) {
|
|
544
|
+
throw new Error("Local embedding returned non-numeric or infinite values");
|
|
545
|
+
}
|
|
546
|
+
if (expectedDims > 0 && vec.length !== expectedDims) {
|
|
547
|
+
throw new Error(`Local embedding dimension mismatch: expected ${expectedDims}, got ${vec.length}`);
|
|
548
|
+
}
|
|
549
|
+
vectors.push(vec);
|
|
550
|
+
}
|
|
551
|
+
if (vectors.length !== expectedCount) {
|
|
552
|
+
throw new Error(`Local embedding output count mismatch: expected ${expectedCount}, got ${vectors.length}`);
|
|
553
|
+
}
|
|
554
|
+
return vectors;
|
|
555
|
+
}
|
|
556
|
+
function createOpenAICompatibleEmbeddingProvider(options) {
|
|
557
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "") || "https://api.openai.com/v1";
|
|
558
|
+
let authFailed = false; // Fail-fast: skip all subsequent calls after first 401
|
|
559
|
+
return {
|
|
560
|
+
name: "openaiCompatible",
|
|
561
|
+
model: options.model,
|
|
562
|
+
async embedTexts(texts, requestOptions) {
|
|
563
|
+
if (authFailed)
|
|
564
|
+
throw new Error("Embedding provider auth previously failed — skipping all requests");
|
|
565
|
+
const controller = new AbortController();
|
|
566
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
567
|
+
requestOptions?.signal?.addEventListener("abort", () => controller.abort(), { once: true });
|
|
568
|
+
try {
|
|
569
|
+
const isGemini = baseUrl.includes("generativelanguage.googleapis.com");
|
|
570
|
+
const url = isGemini
|
|
571
|
+
? `${baseUrl}/embeddings?key=${encodeURIComponent(options.apiKey)}`
|
|
572
|
+
: `${baseUrl}/embeddings`;
|
|
573
|
+
const headers = { "content-type": "application/json" };
|
|
574
|
+
if (!isGemini) {
|
|
575
|
+
headers.authorization = `Bearer ${options.apiKey}`;
|
|
576
|
+
}
|
|
577
|
+
// Rate limit: process in small batches (~8 texts/batch, ~400 tokens/batch)
|
|
578
|
+
// to stay under typical 100K tokens/minute rate limits
|
|
579
|
+
const batchSize = 8;
|
|
580
|
+
const results = new Array(texts.length);
|
|
581
|
+
for (let i = 0; i < texts.length; i += batchSize) {
|
|
582
|
+
const batch = texts.slice(i, i + batchSize);
|
|
583
|
+
let lastError = "";
|
|
584
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
585
|
+
try {
|
|
586
|
+
const response = await fetch(url, {
|
|
587
|
+
method: "POST",
|
|
588
|
+
headers,
|
|
589
|
+
body: JSON.stringify({ model: options.model, input: batch }),
|
|
590
|
+
signal: controller.signal
|
|
591
|
+
});
|
|
592
|
+
if (response.status === 429 && attempt < 2) {
|
|
593
|
+
lastError = await response.text().catch(() => "rate limited");
|
|
594
|
+
await new Promise(r => setTimeout(r, 5000 * (attempt + 1)));
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
// Fail-fast on auth errors: mark provider dead, don't retry
|
|
598
|
+
if (response.status === 401 || response.status === 403) {
|
|
599
|
+
authFailed = true;
|
|
600
|
+
const body = await response.text().catch(() => "");
|
|
601
|
+
throw new Error(`Embedding auth failed: HTTP ${response.status} — ${body.slice(0, 300)}`);
|
|
602
|
+
}
|
|
603
|
+
if (!response.ok) {
|
|
604
|
+
const body = await response.text().catch(() => "");
|
|
605
|
+
throw new Error(`Embedding request failed: HTTP ${response.status}${body ? ` — ${body.slice(0, 200)}` : ""}`);
|
|
606
|
+
}
|
|
607
|
+
const payload = await response.json();
|
|
608
|
+
const embeddings = payload.data?.map((item) => item.embedding).filter((item) => Array.isArray(item));
|
|
609
|
+
if (!embeddings || embeddings.length !== batch.length) {
|
|
610
|
+
// Fallback: try Gemini response format
|
|
611
|
+
const geminiEmbeddings = payload?.embeddings?.map((e) => e.values)?.filter((e) => Array.isArray(e));
|
|
612
|
+
if (geminiEmbeddings && geminiEmbeddings.length === batch.length) {
|
|
613
|
+
geminiEmbeddings.forEach((vec, idx) => { results[i + idx] = vec; });
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
throw new Error("Embedding response did not match requested input count.");
|
|
617
|
+
}
|
|
618
|
+
embeddings.forEach((vec, idx) => { results[i + idx] = vec; });
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
catch (e) {
|
|
622
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
623
|
+
if (attempt === 2 || (!msg.includes("429") && !msg.includes("rate")))
|
|
624
|
+
throw e;
|
|
625
|
+
lastError = msg;
|
|
626
|
+
await new Promise(r => setTimeout(r, 5000 * (attempt + 1)));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Delay between batches: ~3s for 8 texts ≈ 400 tokens = well under 100K/min
|
|
630
|
+
if (i + batchSize < texts.length) {
|
|
631
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return results.filter((r) => Array.isArray(r));
|
|
635
|
+
}
|
|
636
|
+
finally {
|
|
637
|
+
clearTimeout(timeout);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function createGeminiEmbeddingProvider(options) {
|
|
643
|
+
const modelId = options.model || "gemini-embedding-001";
|
|
644
|
+
let authFailed = false;
|
|
645
|
+
return {
|
|
646
|
+
name: "gemini",
|
|
647
|
+
model: options.model,
|
|
648
|
+
async embedTexts(texts, requestOptions) {
|
|
649
|
+
if (authFailed)
|
|
650
|
+
throw new Error("Embedding provider auth previously failed — skipping all requests");
|
|
651
|
+
const controller = new AbortController();
|
|
652
|
+
const timeoutTimer = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
653
|
+
requestOptions?.signal?.addEventListener("abort", () => controller.abort(), { once: true });
|
|
654
|
+
try {
|
|
655
|
+
// Gemini embedContent has low free-tier rate limits (~5 RPM)
|
|
656
|
+
// Process sequentially with 1.5s delay between requests
|
|
657
|
+
const results = new Array(texts.length);
|
|
658
|
+
for (let i = 0; i < texts.length; i++) {
|
|
659
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:embedContent?key=${encodeURIComponent(options.apiKey)}`;
|
|
660
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
661
|
+
try {
|
|
662
|
+
const response = await fetch(url, {
|
|
663
|
+
method: "POST",
|
|
664
|
+
headers: { "content-type": "application/json" },
|
|
665
|
+
body: JSON.stringify({
|
|
666
|
+
model: `models/${modelId}`,
|
|
667
|
+
content: { parts: [{ text: texts[i] }] }
|
|
668
|
+
}),
|
|
669
|
+
signal: controller.signal
|
|
670
|
+
});
|
|
671
|
+
if (response.status === 429 && attempt < 2) {
|
|
672
|
+
await new Promise(r => setTimeout(r, 3000 * (attempt + 1)));
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (response.status === 401 || response.status === 403) {
|
|
676
|
+
authFailed = true;
|
|
677
|
+
const body = await response.text().catch(() => "");
|
|
678
|
+
throw new Error(`Gemini embedding auth failed: HTTP ${response.status} — ${body.slice(0, 300)}`);
|
|
679
|
+
}
|
|
680
|
+
if (!response.ok) {
|
|
681
|
+
const body = await response.text().catch(() => "");
|
|
682
|
+
throw new Error(`Gemini embedding failed: HTTP ${response.status} — ${body.slice(0, 300)}`);
|
|
683
|
+
}
|
|
684
|
+
const payload = await response.json();
|
|
685
|
+
const embedding = payload.embedding?.values;
|
|
686
|
+
if (!embedding || !Array.isArray(embedding)) {
|
|
687
|
+
throw new Error("Gemini response missing embedding values");
|
|
688
|
+
}
|
|
689
|
+
results[i] = embedding;
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
catch (e) {
|
|
693
|
+
if (attempt === 2 || (e instanceof Error && !e.message.includes("429")))
|
|
694
|
+
throw e;
|
|
695
|
+
await new Promise(r => setTimeout(r, 3000 * (attempt + 1)));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// Rate limit: 1 request per 1.5s
|
|
699
|
+
if (i < texts.length - 1) {
|
|
700
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return results.filter((r) => Array.isArray(r));
|
|
704
|
+
}
|
|
705
|
+
finally {
|
|
706
|
+
clearTimeout(timeoutTimer);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
// Deterministic test embedding provider — zero network, pure term-hash vectors.
|
|
712
|
+
// Produces 128-dim vectors from token hashing so cosine similarity ≈ term overlap.
|
|
713
|
+
// Only for pipeline smoke/proof; never use in production.
|
|
714
|
+
const DET_DIM = 128;
|
|
715
|
+
const DET_BITS_PER_TERM = 7;
|
|
716
|
+
function createDeterministicEmbeddingProvider(model) {
|
|
717
|
+
return {
|
|
718
|
+
name: "deterministicTest",
|
|
719
|
+
model,
|
|
720
|
+
dimensions: DET_DIM,
|
|
721
|
+
async embedTexts(texts, _requestOptions) {
|
|
722
|
+
return texts.map((text) => deterministicEmbed(text));
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function deterministicEmbed(text) {
|
|
727
|
+
const vec = new Array(DET_DIM).fill(0);
|
|
728
|
+
const tokens = tokenizeForEmbedding(text);
|
|
729
|
+
for (const token of tokens) {
|
|
730
|
+
const h = hashStr(token);
|
|
731
|
+
for (let b = 0; b < DET_BITS_PER_TERM; b++) {
|
|
732
|
+
const dim = ((h >> (b * 5)) & 0x7f) % DET_DIM; // 0..127
|
|
733
|
+
const sign = ((h >> (b * 5 + 4)) & 1) ? 1 : -1;
|
|
734
|
+
vec[dim] += sign;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
// L2 normalize
|
|
738
|
+
let norm = 0;
|
|
739
|
+
for (let i = 0; i < DET_DIM; i++)
|
|
740
|
+
norm += vec[i] * vec[i];
|
|
741
|
+
norm = Math.sqrt(norm) || 1;
|
|
742
|
+
for (let i = 0; i < DET_DIM; i++)
|
|
743
|
+
vec[i] /= norm;
|
|
744
|
+
return vec;
|
|
745
|
+
}
|
|
746
|
+
function tokenizeForEmbedding(text) {
|
|
747
|
+
const lower = text.toLowerCase();
|
|
748
|
+
const tokens = [];
|
|
749
|
+
// Extract identifiers, paths, words
|
|
750
|
+
const wordRe = /[a-z0-9_]{2,}/g;
|
|
751
|
+
let m;
|
|
752
|
+
while ((m = wordRe.exec(lower)) !== null) {
|
|
753
|
+
tokens.push(m[0]);
|
|
754
|
+
// Also add bigrams for path-like strings
|
|
755
|
+
if (m[0].length >= 5) {
|
|
756
|
+
for (let i = 0; i < m[0].length - 1; i++) {
|
|
757
|
+
tokens.push(m[0].slice(i, i + 2));
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// Path segments
|
|
762
|
+
for (const seg of lower.split(/[/\\]/)) {
|
|
763
|
+
if (seg.length >= 2)
|
|
764
|
+
tokens.push(seg);
|
|
765
|
+
}
|
|
766
|
+
return [...new Set(tokens)];
|
|
767
|
+
}
|
|
768
|
+
function hashStr(s) {
|
|
769
|
+
let h = 0;
|
|
770
|
+
for (let i = 0; i < s.length; i++) {
|
|
771
|
+
h = ((h << 5) - h + s.charCodeAt(i)) | 0;
|
|
772
|
+
}
|
|
773
|
+
return h >>> 0; // unsigned 32-bit
|
|
774
|
+
}
|
|
775
|
+
function createConfiguredEmbeddingProvider(config, options) {
|
|
776
|
+
(0, env_1.loadEnvFiles)();
|
|
777
|
+
// Deterministic test provider — no network, no API key needed
|
|
778
|
+
if (config.semanticProvider === "deterministicTest") {
|
|
779
|
+
return createDeterministicEmbeddingProvider(config.semanticModel || "deterministic-term-v1");
|
|
780
|
+
}
|
|
781
|
+
// Local provider — no API key, gated by runtime/model readiness
|
|
782
|
+
if (config.semanticProvider === "local") {
|
|
783
|
+
const workspacePath = config.workspacePath;
|
|
784
|
+
if (!workspacePath)
|
|
785
|
+
return undefined;
|
|
786
|
+
const readiness = getLocalProviderReadiness(workspacePath, {
|
|
787
|
+
semanticModel: config.semanticModel,
|
|
788
|
+
semanticLocalModelPath: config.semanticLocalModelPath,
|
|
789
|
+
semanticDimensions: config.semanticDimensions
|
|
790
|
+
});
|
|
791
|
+
// Bundled package path
|
|
792
|
+
if (readiness.localStatus === "bundled_package_ready") {
|
|
793
|
+
const bundled = detectBundledLocalPackage(workspacePath);
|
|
794
|
+
if (bundled.valid && bundled.packagePath && bundled.modelId) {
|
|
795
|
+
return createLocalEmbeddingProvider(workspacePath, {
|
|
796
|
+
modelId: bundled.modelId,
|
|
797
|
+
modelPath: bundled.packagePath,
|
|
798
|
+
dimensions: bundled.dimensions || 0,
|
|
799
|
+
packageRoot: bundled.packagePath
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return undefined;
|
|
803
|
+
}
|
|
804
|
+
if (readiness.localStatus !== "local_ready")
|
|
805
|
+
return undefined;
|
|
806
|
+
// Read manifest for trusted model id and dimensions
|
|
807
|
+
let modelId = config.semanticModel || "sentence-transformers/all-MiniLM-L6-v2";
|
|
808
|
+
let dims = config.semanticDimensions || 0;
|
|
809
|
+
const modelPath = config.semanticLocalModelPath || "";
|
|
810
|
+
if (modelPath) {
|
|
811
|
+
try {
|
|
812
|
+
const manifestRaw = fs.readFileSync(path.join(modelPath, "manifest.json"), "utf8");
|
|
813
|
+
const manifest = JSON.parse(manifestRaw);
|
|
814
|
+
if (manifest.modelId && typeof manifest.modelId === "string")
|
|
815
|
+
modelId = manifest.modelId;
|
|
816
|
+
if (manifest.dimensions !== undefined && typeof manifest.dimensions === "number" && manifest.dimensions > 0)
|
|
817
|
+
dims = manifest.dimensions;
|
|
818
|
+
}
|
|
819
|
+
catch { /* use config values as fallback */ }
|
|
820
|
+
}
|
|
821
|
+
// Prefer explicit semanticLocalModelId over manifest modelId
|
|
822
|
+
if (config.semanticLocalModelId)
|
|
823
|
+
modelId = config.semanticLocalModelId;
|
|
824
|
+
return createLocalEmbeddingProvider(workspacePath, {
|
|
825
|
+
modelId,
|
|
826
|
+
modelPath,
|
|
827
|
+
dimensions: dims
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
const apiKey = process.env[config.semanticApiKeyEnv];
|
|
831
|
+
if (!apiKey) {
|
|
832
|
+
return undefined;
|
|
833
|
+
}
|
|
834
|
+
// Detect Gemini provider by base URL or model name
|
|
835
|
+
if (config.semanticBaseUrl.includes("generativelanguage.googleapis.com") ||
|
|
836
|
+
config.semanticModel.includes("embedding-") ||
|
|
837
|
+
config.semanticModel.includes("gemini")) {
|
|
838
|
+
return createGeminiEmbeddingProvider({
|
|
839
|
+
apiKey,
|
|
840
|
+
baseUrl: config.semanticBaseUrl,
|
|
841
|
+
model: config.semanticModel,
|
|
842
|
+
timeoutMs: config.semanticTimeoutMs
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
return createOpenAICompatibleEmbeddingProvider({
|
|
846
|
+
apiKey,
|
|
847
|
+
baseUrl: config.semanticBaseUrl,
|
|
848
|
+
model: config.semanticModel,
|
|
849
|
+
timeoutMs: config.semanticTimeoutMs
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
//# sourceMappingURL=embeddingProviders.js.map
|