thumbgate 1.34.3 → 1.37.1
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/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
- package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
- package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
- package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
- package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +1 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/CONTRIBUTING.md +95 -0
- package/README.md +195 -632
- package/THIRD_PARTY_NOTICES.md +89 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/future-agi/.mcp.json +8 -0
- package/adapters/future-agi/FUTURE_AGI.md +23 -0
- package/adapters/future-agi/config.toml +3 -0
- package/adapters/future-agi/future-agi-bridge.js +9 -0
- package/adapters/future-agi/opencode.json +8 -0
- package/adapters/herdr/herdr-plugin.toml +18 -0
- package/adapters/mcp/server-stdio.js +238 -25
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/workos/WORKOS.md +52 -0
- package/bin/cli.js +373 -5
- package/bin/futureagi-bridge +9 -0
- package/config/gate-templates.json +653 -4
- package/config/gates/actor-critic-audit.json +34 -0
- package/config/gates/default.json +21 -2
- package/config/gates/five-walls-governance.json +34 -0
- package/config/gates/future-agi-guardrails.json +34 -0
- package/config/gates/radware-threat-defense-2026.json +61 -0
- package/config/gates/simatree-data-governance.json +33 -0
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +10 -1
- package/config/model-candidates.json +382 -24
- package/config/model-tiers.json +18 -0
- package/config/post-deploy-marketing-pages.json +10 -0
- package/config/progressive/01-wire-only.json +11 -0
- package/config/progressive/02-dashboard-empty-ok.json +10 -0
- package/config/progressive/03-one-lesson.json +10 -0
- package/config/progressive/04-warn-fires.json +11 -0
- package/config/progressive/05-strict-optional.json +11 -0
- package/config/progressive/README.md +15 -0
- package/config/schemas/broker-execution-receipt.schema.json +139 -0
- package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
- package/conformance/provider-attestation/vectors.json +320 -0
- package/docs/specs/provider-execution-attestation-v1.md +69 -0
- package/openapi/openapi.yaml +15 -0
- package/package.json +401 -147
- package/public/about.html +2 -2
- package/public/ai-malpractice-prevention.html +7 -7
- package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
- package/public/blog/a-receipt-is-not-world-state.html +388 -0
- package/public/blog/git-at-agent-scale.html +374 -0
- package/public/blog/no-llm-in-the-gate.html +133 -0
- package/public/blog.html +80 -0
- package/public/case-studies.html +16 -1
- package/public/compare.html +28 -0
- package/public/diagnostic.html +216 -7
- package/public/docs/connectors.html +39 -0
- package/public/federal.html +2 -2
- package/public/founders.html +639 -0
- package/public/index.html +87 -9
- package/public/install.html +8 -8
- package/public/learn.html +39 -0
- package/public/numbers.html +2 -2
- package/public/peter.html +310 -0
- package/public/platform-partners.html +119 -0
- package/public/pricing.html +24 -3
- package/public/privacy.html +117 -0
- package/public/pro.html +17 -0
- package/public/support.html +62 -0
- package/public/terms.html +130 -0
- package/public/third-party-notices.html +95 -0
- package/public/yt.html +351 -0
- package/scripts/action-receipts.js +133 -3
- package/scripts/adaptive-governance-arena.js +349 -0
- package/scripts/admin-override.js +205 -0
- package/scripts/agent-action-inventory.js +869 -0
- package/scripts/agent-audit-trace.js +42 -2
- package/scripts/agent-egress-policy.js +1117 -0
- package/scripts/agent-memory-lifecycle.js +141 -2
- package/scripts/agent-operations-planner.js +441 -1
- package/scripts/agent-readiness.js +68 -0
- package/scripts/agent-security-central.js +647 -0
- package/scripts/allowlist-bridge-honesty.js +417 -0
- package/scripts/async-job-runner.js +102 -11
- package/scripts/audit-trail.js +212 -0
- package/scripts/auto-promote-gates.js +178 -27
- package/scripts/billing.js +1 -1
- package/scripts/broker-execution-receipts.js +719 -0
- package/scripts/budget-aware-gates-proof.js +423 -0
- package/scripts/claude-feedback-sync.js +29 -3
- package/scripts/claw-harness-production.js +237 -0
- package/scripts/cli-progress.js +111 -0
- package/scripts/cli-schema.js +163 -1
- package/scripts/codex-runbook-flywheel.js +318 -0
- package/scripts/context-footprint.js +186 -0
- package/scripts/contextfs.js +143 -61
- package/scripts/dashboard-limits.js +27 -0
- package/scripts/dashboard.js +279 -9
- package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
- package/scripts/docker-sandbox-planner.js +18 -0
- package/scripts/double-blind-eval-protocol.js +252 -0
- package/scripts/edotenv-rl-gateway.js +259 -0
- package/scripts/ensure-production-search-corpus.js +162 -0
- package/scripts/eval-holdout.js +311 -0
- package/scripts/feedback-aggregate.js +21 -2
- package/scripts/feedback-loop.js +87 -5
- package/scripts/feedback-quality.js +9 -0
- package/scripts/file-ledger-lock.js +4 -1
- package/scripts/financial-control-plane.js +41 -1
- package/scripts/find-dormant-requires.js +118 -0
- package/scripts/fs-utils.js +84 -8
- package/scripts/gate-stats.js +2 -2
- package/scripts/gates-engine.js +859 -58
- package/scripts/generate-case-study-outreach.js +24 -15
- package/scripts/git-at-scale.js +628 -0
- package/scripts/governance-conflict-audit.js +1650 -0
- package/scripts/governance-difficulty-curriculum.js +328 -0
- package/scripts/graphrag-retrieval.js +275 -0
- package/scripts/gurobi-optimizer.js +324 -0
- package/scripts/gurobi_optimizer.py +485 -0
- package/scripts/harness-selector.js +82 -1
- package/scripts/hidden-entry-points.js +284 -0
- package/scripts/human-escalation.js +199 -1
- package/scripts/hybrid-feedback-context.js +152 -19
- package/scripts/intent-governed-execution.js +602 -0
- package/scripts/intervention-policy.js +123 -20
- package/scripts/jit-harness-compose.js +628 -0
- package/scripts/jsonl-watcher.js +10 -0
- package/scripts/lesson-embedding-index.js +95 -12
- package/scripts/lesson-retrieval.js +105 -19
- package/scripts/local-model-profile.js +19 -2
- package/scripts/mailer/resend-mailer.js +1 -1
- package/scripts/matryoshka-embedding.js +235 -0
- package/scripts/mcp-oauth.js +42 -4
- package/scripts/mcp-session-handles.js +1016 -0
- package/scripts/mcp-wiring-doctor.js +314 -0
- package/scripts/memory-firewall.js +115 -2
- package/scripts/memory-scope-readiness.js +299 -0
- package/scripts/memory-vs-rag-route.js +161 -0
- package/scripts/model-tier-router.js +148 -21
- package/scripts/nvidia-specdecode-al-doctor.js +536 -0
- package/scripts/openui-catalog-compose-honesty.js +593 -0
- package/scripts/operational-integrity.js +19 -1
- package/scripts/override-audit.js +213 -0
- package/scripts/package-manager-honesty-doctor.js +458 -0
- package/scripts/pr-manager.js +63 -1
- package/scripts/prove-herdr-adapter.js +52 -0
- package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
- package/scripts/prove-workos.js +73 -0
- package/scripts/provider-attestation-conformance.js +192 -0
- package/scripts/provider-receipt-contract.js +136 -0
- package/scripts/qwen38-max-cost-optimizer.js +401 -0
- package/scripts/radware-threat-defense.js +280 -0
- package/scripts/rag-embedding-identity.js +221 -0
- package/scripts/rag-precision-guardrails.js +112 -2
- package/scripts/remote-feedback-capture.js +159 -0
- package/scripts/research-agent-harness.js +256 -0
- package/scripts/rsi-safety-hillclimb.js +200 -0
- package/scripts/rule-sprawl.js +188 -0
- package/scripts/schedule-manager.js +147 -0
- package/scripts/self-heal.js +8 -0
- package/scripts/session-lease.js +415 -0
- package/scripts/simatree-data-governance.js +347 -0
- package/scripts/slo-alert-engine.js +172 -7
- package/scripts/solver-parity.js +539 -0
- package/scripts/stealth-memory-injection-gate.js +333 -0
- package/scripts/switchyard-router.js +366 -0
- package/scripts/telemetry-analytics.js +84 -27
- package/scripts/temporal-decay-weighting.js +138 -0
- package/scripts/test-all.js +165 -0
- package/scripts/token-savings.js +42 -0
- package/scripts/tool-kpi-tracker.js +108 -5
- package/scripts/tool-registry.js +193 -5
- package/scripts/universal-claim-evaluator.js +14 -2
- package/scripts/vector-store.js +279 -9
- package/scripts/workflow-notebook.js +391 -0
- package/scripts/workflow-sentinel.js +111 -12
- package/scripts/workos-production-guard.js +260 -0
- package/scripts/workspace-search-route.js +515 -0
- package/server.json +2 -2
- package/src/agent-identity-boundary.js +76 -0
- package/src/agent-retrieval-cache.js +155 -0
- package/src/alert-noise-ledger.js +502 -0
- package/src/api/server.js +802 -185
- package/src/git-fast-cache.js +220 -0
- package/src/git-wal-sync.js +156 -0
- package/src/hash-anchored-edit.js +82 -0
- package/src/hermes-platform-protocol.js +475 -0
- package/src/hermes-sync-plane.js +241 -0
- package/src/index.js +30 -1
- package/src/iso42001-compliance-guard.js +97 -0
- package/src/latency-budget.js +244 -0
- package/src/mcp-writeguard.js +316 -0
- package/src/miminions-adapter.js +106 -0
- package/src/pipeline-compass.js +104 -0
- package/src/ppl-alert-pipeline.js +284 -0
- package/src/rendezvous-router.js +90 -0
- package/src/security-questionnaire.js +195 -0
package/scripts/vector-store.js
CHANGED
|
@@ -31,6 +31,60 @@ let _geminiEmbedderForTests = null;
|
|
|
31
31
|
const TABLE_NAME = 'thumbgate_memories';
|
|
32
32
|
const FEATURE_HASH_DIMENSIONS = 384;
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Matryoshka / MRL-style dimensionality reduction: keep leading dims and
|
|
37
|
+
* re-L2-normalize so cosine similarity stays valid after truncation.
|
|
38
|
+
* Safe no-op when target is missing, non-positive, or >= full vector length.
|
|
39
|
+
*/
|
|
40
|
+
function l2NormalizeVector(vector) {
|
|
41
|
+
if (!Array.isArray(vector) || vector.length === 0) return vector;
|
|
42
|
+
const numbers = vector.map(Number);
|
|
43
|
+
const norm = Math.sqrt(numbers.reduce((sum, value) => sum + (value * value), 0));
|
|
44
|
+
if (!Number.isFinite(norm) || norm === 0) {
|
|
45
|
+
const zero = numbers.slice();
|
|
46
|
+
zero[0] = 1;
|
|
47
|
+
return zero;
|
|
48
|
+
}
|
|
49
|
+
return numbers.map((value) => value / norm);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveOutputDimensionality(options = {}, env = process.env) {
|
|
53
|
+
const provider = String(options.provider || options.embeddingProvider || '').toLowerCase();
|
|
54
|
+
const geminiOnly = provider === 'gemini' || options.geminiManaged === true;
|
|
55
|
+
const candidates = [
|
|
56
|
+
options.outputDimensionality,
|
|
57
|
+
options.dimensions,
|
|
58
|
+
options.matryoshkaDim,
|
|
59
|
+
env.THUMBGATE_MATRYOSHKA_DIM,
|
|
60
|
+
env.THUMBGATE_EMBED_DIM,
|
|
61
|
+
];
|
|
62
|
+
// Gemini-only dim must not reshape Ollama / transformers / feature-hash vectors.
|
|
63
|
+
if (geminiOnly) {
|
|
64
|
+
candidates.push(env.THUMBGATE_GEMINI_EMBED_DIM);
|
|
65
|
+
}
|
|
66
|
+
for (const candidate of candidates) {
|
|
67
|
+
if (candidate === undefined || candidate === null || candidate === '') continue;
|
|
68
|
+
const parsed = Number(candidate);
|
|
69
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function applyMatryoshkaDimensionality(vector, targetDim) {
|
|
75
|
+
if (!Array.isArray(vector) || vector.length === 0) return vector;
|
|
76
|
+
const dim = Number(targetDim);
|
|
77
|
+
if (!Number.isFinite(dim) || dim <= 0 || dim >= vector.length) {
|
|
78
|
+
return vector.map(Number);
|
|
79
|
+
}
|
|
80
|
+
return l2NormalizeVector(vector.slice(0, Math.floor(dim)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function finalizeEmbedding(vector, options = {}) {
|
|
84
|
+
return applyMatryoshkaDimensionality(vector, resolveOutputDimensionality(options));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
34
88
|
async function getLanceDB() {
|
|
35
89
|
if (!_lancedb) {
|
|
36
90
|
_lancedb = _lancedbLoader ? await _lancedbLoader() : await import('@lancedb/lancedb');
|
|
@@ -105,8 +159,125 @@ function getOllamaEmbeddingConfig() {
|
|
|
105
159
|
* Synchronous capability check used by hot-path routing. This reports only
|
|
106
160
|
* semantic providers, never the deterministic feature-hash degradation.
|
|
107
161
|
*/
|
|
162
|
+
function getOpenAICompatibleEmbeddingConfig(env = process.env) {
|
|
163
|
+
const providerHint = String(env.THUMBGATE_EMBED_PROVIDER || '').trim().toLowerCase();
|
|
164
|
+
const baseUrlRaw = String(
|
|
165
|
+
env.THUMBGATE_OPENAI_EMBED_BASE_URL
|
|
166
|
+
|| env.DASHSCOPE_BASE_URL
|
|
167
|
+
|| '',
|
|
168
|
+
).trim();
|
|
169
|
+
const isDashscopeHint = ['dashscope', 'qwen', 'model-studio', 'openai-compatible'].includes(providerHint)
|
|
170
|
+
|| /dashscope/i.test(baseUrlRaw);
|
|
171
|
+
const apiKey = String(
|
|
172
|
+
env.THUMBGATE_OPENAI_EMBED_API_KEY
|
|
173
|
+
|| env.DASHSCOPE_API_KEY
|
|
174
|
+
|| env.QWEN_API_KEY
|
|
175
|
+
|| '',
|
|
176
|
+
).trim();
|
|
177
|
+
let baseUrl = baseUrlRaw
|
|
178
|
+
|| (isDashscopeHint ? 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1' : '');
|
|
179
|
+
if (baseUrl && !/^https?:\/\//i.test(baseUrl)) baseUrl = `https://${baseUrl}`;
|
|
180
|
+
while (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1);
|
|
181
|
+
|
|
182
|
+
const model = String(
|
|
183
|
+
env.THUMBGATE_OPENAI_EMBED_MODEL
|
|
184
|
+
|| env.THUMBGATE_QWEN_EMBED_MODEL
|
|
185
|
+
|| (isDashscopeHint ? 'text-embedding-v4' : ''),
|
|
186
|
+
).trim();
|
|
187
|
+
const dimRaw = Number(env.THUMBGATE_OPENAI_EMBED_DIM || env.THUMBGATE_QWEN_EMBED_DIM || 0);
|
|
188
|
+
const dimensions = Number.isFinite(dimRaw) && dimRaw > 0 ? Math.floor(dimRaw) : null;
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
enabled: Boolean(apiKey && baseUrl && model && (isDashscopeHint || providerHint === 'openai-compatible' || baseUrlRaw)),
|
|
192
|
+
provider: /dashscope/i.test(baseUrl) || isDashscopeHint ? 'dashscope' : 'openai-compatible',
|
|
193
|
+
apiKey,
|
|
194
|
+
baseUrl,
|
|
195
|
+
model,
|
|
196
|
+
dimensions,
|
|
197
|
+
endpoint: baseUrl ? `${baseUrl}/embeddings` : null,
|
|
198
|
+
timeoutMs: Math.max(
|
|
199
|
+
250,
|
|
200
|
+
Math.min(30_000, Number(env.THUMBGATE_OPENAI_EMBED_TIMEOUT_MS) || 15_000),
|
|
201
|
+
),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let _openaiEmbedderForTests = null;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* OpenAI-compatible embeddings (DashScope text-embedding-v4 / Model Studio).
|
|
209
|
+
* Supports Matryoshka dims when the provider accepts `dimensions`.
|
|
210
|
+
*/
|
|
211
|
+
async function embedWithOpenAICompatible(text, options = {}) {
|
|
212
|
+
const config = options.config || getOpenAICompatibleEmbeddingConfig(options.env || process.env);
|
|
213
|
+
if (!config.enabled && !_openaiEmbedderForTests) {
|
|
214
|
+
throw new Error('OpenAI-compatible embeddings requested but base URL, model, or API key is missing');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (_openaiEmbedderForTests) {
|
|
218
|
+
return _openaiEmbedderForTests(text, config, options);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (typeof fetch !== 'function') {
|
|
222
|
+
throw new TypeError('OpenAI-compatible embeddings require global fetch (Node 18.18+).');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const body = {
|
|
226
|
+
model: config.model,
|
|
227
|
+
input: String(text || ''),
|
|
228
|
+
};
|
|
229
|
+
const dim = resolveOutputDimensionality({
|
|
230
|
+
...options,
|
|
231
|
+
provider: config.provider,
|
|
232
|
+
dimensions: options.dimensions || config.dimensions,
|
|
233
|
+
}) || config.dimensions;
|
|
234
|
+
if (dim) body.dimensions = dim;
|
|
235
|
+
|
|
236
|
+
const controller = new AbortController();
|
|
237
|
+
const timer = setTimeout(() => controller.abort(), config.timeoutMs);
|
|
238
|
+
let response;
|
|
239
|
+
try {
|
|
240
|
+
response = await fetch(config.endpoint, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: {
|
|
243
|
+
'Content-Type': 'application/json',
|
|
244
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
245
|
+
},
|
|
246
|
+
body: JSON.stringify(body),
|
|
247
|
+
signal: controller.signal,
|
|
248
|
+
});
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw new Error(`OpenAI-compatible embedding service unavailable: ${error.message}`);
|
|
251
|
+
} finally {
|
|
252
|
+
clearTimeout(timer);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!response.ok) {
|
|
256
|
+
const errBody = await response.text().catch(() => '');
|
|
257
|
+
throw new Error(
|
|
258
|
+
`OpenAI-compatible embedding request failed: ${response.status} ${response.statusText}`
|
|
259
|
+
+ `${errBody ? ` — ${errBody.slice(0, 240)}` : ''}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const payload = await response.json();
|
|
264
|
+
const values = payload
|
|
265
|
+
&& Array.isArray(payload.data)
|
|
266
|
+
&& payload.data[0]
|
|
267
|
+
&& Array.isArray(payload.data[0].embedding)
|
|
268
|
+
? payload.data[0].embedding
|
|
269
|
+
: null;
|
|
270
|
+
|
|
271
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
272
|
+
throw new Error('OpenAI-compatible embedding response did not include vector values');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return values.map(Number);
|
|
276
|
+
}
|
|
277
|
+
|
|
108
278
|
function hasSemanticEmbeddingProvider() {
|
|
109
279
|
if (process.env.THUMBGATE_VECTOR_STUB_EMBED === 'true') return true;
|
|
280
|
+
if (getOpenAICompatibleEmbeddingConfig().enabled) return true;
|
|
110
281
|
if (getOllamaEmbeddingConfig().enabled) return true;
|
|
111
282
|
if (hasLocalTransformerProvider()) return true;
|
|
112
283
|
try {
|
|
@@ -221,7 +392,7 @@ async function embedWithGemini(text, options = {}) {
|
|
|
221
392
|
content: {
|
|
222
393
|
parts: [{ text: preparedText }],
|
|
223
394
|
},
|
|
224
|
-
outputDimensionality: config.outputDimensionality,
|
|
395
|
+
outputDimensionality: resolveOutputDimensionality(options) || config.outputDimensionality,
|
|
225
396
|
};
|
|
226
397
|
const taskType = resolveGeminiTaskType({
|
|
227
398
|
kind: options.kind,
|
|
@@ -315,7 +486,7 @@ async function embedWithOllama(text, options = {}) {
|
|
|
315
486
|
model: config.model,
|
|
316
487
|
input: inputText,
|
|
317
488
|
truncate: true,
|
|
318
|
-
dimensions: options
|
|
489
|
+
dimensions: resolveOutputDimensionality(options) || undefined,
|
|
319
490
|
}),
|
|
320
491
|
signal: AbortSignal.timeout(config.timeoutMs),
|
|
321
492
|
});
|
|
@@ -372,7 +543,7 @@ async function embed(text, options = {}) {
|
|
|
372
543
|
// Deterministic 384-dim unit vector: first element = 1.0, rest = 0.0
|
|
373
544
|
const stub = Array(384).fill(0);
|
|
374
545
|
stub[0] = 1.0;
|
|
375
|
-
return stub;
|
|
546
|
+
return finalizeEmbedding(stub, options);
|
|
376
547
|
}
|
|
377
548
|
const geminiConfig = resolveGeminiEmbeddingConfig();
|
|
378
549
|
if (geminiConfig.provider === 'coreai') {
|
|
@@ -390,11 +561,41 @@ async function embed(text, options = {}) {
|
|
|
390
561
|
},
|
|
391
562
|
fallbackUsed: false,
|
|
392
563
|
};
|
|
393
|
-
return vector;
|
|
564
|
+
return finalizeEmbedding(vector, options);
|
|
394
565
|
} catch (coreaiError) {
|
|
395
566
|
console.warn(`Core AI embedding failed, falling back to local: ${coreaiError.message}`);
|
|
396
567
|
}
|
|
397
568
|
}
|
|
569
|
+
const openaiEmbedConfig = getOpenAICompatibleEmbeddingConfig();
|
|
570
|
+
if (openaiEmbedConfig.enabled) {
|
|
571
|
+
try {
|
|
572
|
+
const vector = await embedWithOpenAICompatible(text, {
|
|
573
|
+
...options,
|
|
574
|
+
config: openaiEmbedConfig,
|
|
575
|
+
});
|
|
576
|
+
_lastEmbeddingProfile = {
|
|
577
|
+
generatedAt: new Date().toISOString(),
|
|
578
|
+
source: openaiEmbedConfig.provider === 'dashscope' ? 'dashscope' : 'openai-compatible',
|
|
579
|
+
activeProfile: {
|
|
580
|
+
id: openaiEmbedConfig.provider,
|
|
581
|
+
model: openaiEmbedConfig.model,
|
|
582
|
+
outputDimensionality: vector.length,
|
|
583
|
+
task: options.task || 'code retrieval',
|
|
584
|
+
rationale: 'OpenAI-compatible embedding (DashScope text-embedding-v4 / Model Studio).',
|
|
585
|
+
qualityTier: 'production',
|
|
586
|
+
},
|
|
587
|
+
fallbackUsed: false,
|
|
588
|
+
};
|
|
589
|
+
return finalizeEmbedding(vector, {
|
|
590
|
+
...options,
|
|
591
|
+
provider: openaiEmbedConfig.provider,
|
|
592
|
+
dimensions: openaiEmbedConfig.dimensions,
|
|
593
|
+
});
|
|
594
|
+
} catch (openaiEmbedError) {
|
|
595
|
+
const code = openaiEmbedError && (openaiEmbedError.code || openaiEmbedError.name || 'Error');
|
|
596
|
+
console.warn(`OpenAI-compatible embedding fallback: ${code}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
398
599
|
const ollamaConfig = getOllamaEmbeddingConfig();
|
|
399
600
|
if (ollamaConfig.enabled) {
|
|
400
601
|
try {
|
|
@@ -412,14 +613,14 @@ async function embed(text, options = {}) {
|
|
|
412
613
|
},
|
|
413
614
|
fallbackUsed: false,
|
|
414
615
|
};
|
|
415
|
-
return vector;
|
|
616
|
+
return finalizeEmbedding(vector, options);
|
|
416
617
|
} catch (ollamaError) {
|
|
417
618
|
console.warn(`Ollama embedding failed, falling back: ${ollamaError.message}`);
|
|
418
619
|
}
|
|
419
620
|
}
|
|
420
621
|
if (geminiConfig.enabled) {
|
|
421
622
|
const vector = await tryGeminiManagedEmbedding(text, options, geminiConfig);
|
|
422
|
-
if (vector) return vector;
|
|
623
|
+
if (vector) return finalizeEmbedding(vector, { ...options, provider: 'gemini', geminiManaged: true });
|
|
423
624
|
}
|
|
424
625
|
if (hasLocalTransformerProvider()) {
|
|
425
626
|
try {
|
|
@@ -428,7 +629,7 @@ async function embed(text, options = {}) {
|
|
|
428
629
|
pooling: 'mean',
|
|
429
630
|
normalize: true,
|
|
430
631
|
});
|
|
431
|
-
return Array.from(output.data); // Float32Array -> plain number[] for LanceDB Arrow serialization
|
|
632
|
+
return finalizeEmbedding(Array.from(output.data), options); // Float32Array -> plain number[] for LanceDB Arrow serialization
|
|
432
633
|
} catch (transformerError) {
|
|
433
634
|
console.warn(`Transformers.js embedding fallback: ${transformerError.message}`);
|
|
434
635
|
}
|
|
@@ -439,7 +640,7 @@ async function embed(text, options = {}) {
|
|
|
439
640
|
// so that THUMBGATE_GEMINI_EMBED_FALLBACK_LOCAL=false makes Gemini mandatory.
|
|
440
641
|
if (geminiConfig.apiKey && !geminiConfig.enabled) {
|
|
441
642
|
const vector = await tryGeminiManagedEmbedding(text, options, geminiConfig);
|
|
442
|
-
if (vector) return vector;
|
|
643
|
+
if (vector) return finalizeEmbedding(vector, { ...options, provider: 'gemini', geminiManaged: true });
|
|
443
644
|
}
|
|
444
645
|
|
|
445
646
|
const vector = embedWithFeatureHash(text);
|
|
@@ -459,7 +660,60 @@ async function embed(text, options = {}) {
|
|
|
459
660
|
fallbackUsed: true,
|
|
460
661
|
fallbackReason: 'no_managed_or_transformer_embedder',
|
|
461
662
|
};
|
|
462
|
-
return vector;
|
|
663
|
+
return finalizeEmbedding(vector, options);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
async function ensureTableCompatibleWithVector(db, vector) {
|
|
668
|
+
const dim = Array.isArray(vector) ? vector.length : 0;
|
|
669
|
+
if (!dim) return;
|
|
670
|
+
const tableNames = await db.tableNames();
|
|
671
|
+
if (!tableNames.includes(TABLE_NAME)) return;
|
|
672
|
+
try {
|
|
673
|
+
const table = await db.openTable(TABLE_NAME);
|
|
674
|
+
// Probe one row for stored vector length when available.
|
|
675
|
+
let storedDim = null;
|
|
676
|
+
if (typeof table.countRows === 'function') {
|
|
677
|
+
const count = await table.countRows();
|
|
678
|
+
if (count === 0) return;
|
|
679
|
+
}
|
|
680
|
+
if (typeof table.query === 'function') {
|
|
681
|
+
try {
|
|
682
|
+
const rows = await table.query().limit(1).toArray();
|
|
683
|
+
if (rows && rows[0] && Array.isArray(rows[0].vector)) {
|
|
684
|
+
storedDim = rows[0].vector.length;
|
|
685
|
+
}
|
|
686
|
+
} catch {
|
|
687
|
+
/* fall through to schema path */
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (storedDim == null && table.schema) {
|
|
691
|
+
try {
|
|
692
|
+
const schema = typeof table.schema === 'function' ? await table.schema() : table.schema;
|
|
693
|
+
const vectorField = schema && (schema.fields || schema).find
|
|
694
|
+
? (schema.fields || schema).find((f) => f.name === 'vector' || f.name === 'embedding')
|
|
695
|
+
: null;
|
|
696
|
+
if (vectorField && vectorField.type && Number(vectorField.type.listSize || vectorField.type.length)) {
|
|
697
|
+
storedDim = Number(vectorField.type.listSize || vectorField.type.length);
|
|
698
|
+
}
|
|
699
|
+
} catch {
|
|
700
|
+
/* ignore */
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (storedDim != null && storedDim !== dim) {
|
|
704
|
+
console.warn(
|
|
705
|
+
`[vector-store] Embedding dimension changed (${storedDim} -> ${dim}); dropping ${TABLE_NAME} for full reindex.`,
|
|
706
|
+
);
|
|
707
|
+
if (typeof db.dropTable === 'function') {
|
|
708
|
+
await db.dropTable(TABLE_NAME);
|
|
709
|
+
} else if (typeof table.delete === 'function') {
|
|
710
|
+
// Best-effort wipe when drop is unavailable.
|
|
711
|
+
await table.delete('true');
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} catch (err) {
|
|
715
|
+
console.warn(`[vector-store] dimension compatibility check failed: ${err && err.message ? err.message : err}`);
|
|
716
|
+
}
|
|
463
717
|
}
|
|
464
718
|
|
|
465
719
|
async function upsertFeedback(feedbackEvent) {
|
|
@@ -505,6 +759,7 @@ async function upsertFeedback(feedbackEvent) {
|
|
|
505
759
|
retries: 2,
|
|
506
760
|
logger: (msg) => console.warn(msg),
|
|
507
761
|
}, async () => {
|
|
762
|
+
await ensureTableCompatibleWithVector(db, vector);
|
|
508
763
|
const tableNames = await db.tableNames();
|
|
509
764
|
if (tableNames.includes(TABLE_NAME)) {
|
|
510
765
|
const table = await db.openTable(TABLE_NAME);
|
|
@@ -529,6 +784,9 @@ async function searchSimilar(queryText, limit = 5) {
|
|
|
529
784
|
kind: 'query',
|
|
530
785
|
task: 'code retrieval',
|
|
531
786
|
});
|
|
787
|
+
await ensureTableCompatibleWithVector(db, vector);
|
|
788
|
+
const tableNamesAfter = await db.tableNames();
|
|
789
|
+
if (!tableNamesAfter.includes(TABLE_NAME)) return [];
|
|
532
790
|
const table = await db.openTable(TABLE_NAME);
|
|
533
791
|
const results = await table.search(vector).limit(limit).toArray();
|
|
534
792
|
return results;
|
|
@@ -561,6 +819,11 @@ function setGeminiEmbedderForTests(loader) {
|
|
|
561
819
|
_lastEmbeddingProfile = null;
|
|
562
820
|
}
|
|
563
821
|
|
|
822
|
+
function setOpenAICompatibleEmbedderForTests(loader) {
|
|
823
|
+
_openaiEmbedderForTests = loader;
|
|
824
|
+
_lastEmbeddingProfile = null;
|
|
825
|
+
}
|
|
826
|
+
|
|
564
827
|
module.exports = {
|
|
565
828
|
upsertFeedback,
|
|
566
829
|
searchSimilar,
|
|
@@ -572,9 +835,16 @@ module.exports = {
|
|
|
572
835
|
setPipelineLoaderForTests,
|
|
573
836
|
setLanceLoaderForTests,
|
|
574
837
|
setGeminiEmbedderForTests,
|
|
838
|
+
setOpenAICompatibleEmbedderForTests,
|
|
575
839
|
truncateForEmbedding,
|
|
576
840
|
embedWithFeatureHash,
|
|
577
841
|
embedWithOllama,
|
|
842
|
+
embedWithOpenAICompatible,
|
|
578
843
|
getOllamaEmbeddingConfig,
|
|
844
|
+
getOpenAICompatibleEmbeddingConfig,
|
|
579
845
|
hasSemanticEmbeddingProvider,
|
|
846
|
+
l2NormalizeVector,
|
|
847
|
+
resolveOutputDimensionality,
|
|
848
|
+
applyMatryoshkaDimensionality,
|
|
849
|
+
finalizeEmbedding,
|
|
580
850
|
};
|