gitnexus 1.6.9-rc.40 → 1.6.9-rc.41

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.
Files changed (36) hide show
  1. package/dist/cli/doctor.js +11 -0
  2. package/dist/core/embeddings/embedder.js +8 -77
  3. package/dist/core/embeddings/onnxruntime-common-resolver.js +24 -19
  4. package/dist/core/embeddings/onnxruntime-node-resolver.d.ts +40 -0
  5. package/dist/core/embeddings/onnxruntime-node-resolver.js +297 -0
  6. package/dist/mcp/core/embedder.js +8 -0
  7. package/package.json +1 -1
  8. package/scripts/cross-platform-tests.ts +1 -0
  9. package/web/assets/{agent-Ci8thBaq.js → agent-PekRmfZA.js} +63 -63
  10. package/web/assets/{architectureDiagram-UL44E2DR-DWIyty1U.js → architectureDiagram-UL44E2DR-BT7OtZ21.js} +1 -1
  11. package/web/assets/{chunk-LCXTWHL2-D1wx7GlK.js → chunk-LCXTWHL2-BaR-mDRh.js} +1 -1
  12. package/web/assets/{chunk-RG4AUYOV-BiC2Wb0O.js → chunk-RG4AUYOV-C3Zi_WJJ.js} +1 -1
  13. package/web/assets/{classDiagram-KGZ6W3CR-CJO2YCOV.js → classDiagram-KGZ6W3CR-DwCv2Ceh.js} +1 -1
  14. package/web/assets/{classDiagram-v2-72OJOZXJ-CJO2YCOV.js → classDiagram-v2-72OJOZXJ-DwCv2Ceh.js} +1 -1
  15. package/web/assets/{dagre-ND4H6XIP-DhPMcW4z.js → dagre-ND4H6XIP-Bbr-DOod.js} +1 -1
  16. package/web/assets/{diagram-3NCE3AQN-CvDoAp_x.js → diagram-3NCE3AQN-t2SsI_89.js} +1 -1
  17. package/web/assets/{diagram-GF46GFSD-Dav8FIQ1.js → diagram-GF46GFSD-DOPCRZb_.js} +1 -1
  18. package/web/assets/{diagram-QXG6HAR7-Bb7602UB.js → diagram-QXG6HAR7-C-ni48MX.js} +1 -1
  19. package/web/assets/{diagram-WEQXMOUZ-C0uHBvgG.js → diagram-WEQXMOUZ-BhkxXWUs.js} +1 -1
  20. package/web/assets/{erDiagram-L5TCEMPS-r6U2VsD8.js → erDiagram-L5TCEMPS-BgU0MxfG.js} +1 -1
  21. package/web/assets/{flowDiagram-H6V6AXG4-N5u_qwIO.js → flowDiagram-H6V6AXG4-aCbzaZWr.js} +1 -1
  22. package/web/assets/{index-D4YESd-F.js → index-CJuHkVdg.js} +7 -7
  23. package/web/assets/{infoDiagram-3YFTVSEB-D_xhhoXs.js → infoDiagram-3YFTVSEB-DVvyoVXw.js} +1 -1
  24. package/web/assets/{ishikawaDiagram-BNXS4ZKH-Wk0TknB-.js → ishikawaDiagram-BNXS4ZKH-hPZmDrPW.js} +1 -1
  25. package/web/assets/{kanban-definition-75IXJCU3-DtfiyBt1.js → kanban-definition-75IXJCU3-pU7f9k-3.js} +1 -1
  26. package/web/assets/{mindmap-definition-2TDM6QVE-kGkn-rwO.js → mindmap-definition-2TDM6QVE-Wq8ESINo.js} +1 -1
  27. package/web/assets/{pieDiagram-CU6KROY3-BMiSBl8M.js → pieDiagram-CU6KROY3-o1D7lwHW.js} +1 -1
  28. package/web/assets/{requirementDiagram-JXO7QTGE-DxhTYbeJ.js → requirementDiagram-JXO7QTGE-BDiLsn6Z.js} +1 -1
  29. package/web/assets/{sequenceDiagram-VS2MUI6T-Cwy_0qTJ.js → sequenceDiagram-VS2MUI6T-DRI70L3-.js} +1 -1
  30. package/web/assets/{stateDiagram-7D4R322I-B6XHUTIG.js → stateDiagram-7D4R322I-BnkJ0BQS.js} +1 -1
  31. package/web/assets/{stateDiagram-v2-36443NZ5-DWBOSVkm.js → stateDiagram-v2-36443NZ5-i2xSyyw9.js} +1 -1
  32. package/web/assets/{timeline-definition-O6YCAMPW-CIRl9vxM.js → timeline-definition-O6YCAMPW-DDbISsUy.js} +1 -1
  33. package/web/assets/{vennDiagram-MWXL3ELB-DQgSBULy.js → vennDiagram-MWXL3ELB-CkSSCEqU.js} +1 -1
  34. package/web/assets/{wardleyDiagram-CUQ6CDDI-BzbU2jy7.js → wardleyDiagram-CUQ6CDDI-CVWX5pm-.js} +1 -1
  35. package/web/assets/{xychartDiagram-N2JHSOCM-BP_6OxzF.js → xychartDiagram-N2JHSOCM-dHr2kp24.js} +1 -1
  36. package/web/index.html +1 -1
@@ -2,6 +2,7 @@ import { getRuntimeCapabilities, getRuntimeFingerprint } from '../core/platform/
2
2
  import { resolveEmbeddingConfig } from '../core/embeddings/config.js';
3
3
  import { isHttpMode } from '../core/embeddings/http-client.js';
4
4
  import { getLocalEmbeddingRuntimeBlocker } from '../core/embeddings/runtime-support.js';
5
+ import { cudaRedirectDoctorStatus } from '../core/embeddings/onnxruntime-node-resolver.js';
5
6
  import { checkLbugNative } from '../core/lbug/native-check.js';
6
7
  import { getExtensionInstallPolicy } from '../core/lbug/extension-loader.js';
7
8
  import { t } from './i18n/index.js';
@@ -119,4 +120,14 @@ export const doctorCommand = async () => {
119
120
  if (support.detail) {
120
121
  process.stderr.write(`\n${support.detail.replace(/^/gm, ' ')}\n\n`);
121
122
  }
123
+ // Surface the CUDA-build-redirect decision so "why is my CUDA-13 host
124
+ // still on CPU" is visible without digging through debug logs (#2341
125
+ // follow-up). Only meaningful on the local runtime path.
126
+ if (!isHttpMode()) {
127
+ const cudaRedirect = cudaRedirectDoctorStatus();
128
+ console.log(` ${padDisplayEnd('CUDA:', 12)}${cudaRedirect.status}`);
129
+ if (cudaRedirect.detail) {
130
+ console.log(` ${padDisplayEnd('', 12)}${cudaRedirect.detail}`);
131
+ }
132
+ }
122
133
  };
@@ -12,89 +12,14 @@
12
12
  if (!process.env.ORT_LOG_LEVEL) {
13
13
  process.env.ORT_LOG_LEVEL = '3';
14
14
  }
15
- import { existsSync } from 'fs';
16
- import { execFileSync } from 'child_process';
17
- import { join, dirname } from 'path';
18
- import { createRequire } from 'module';
19
15
  import { DEFAULT_EMBEDDING_CONFIG } from './types.js';
20
16
  import { isHttpMode, getHttpDimensions, httpEmbed } from './http-client.js';
21
17
  import { resolveEmbeddingConfig } from './config.js';
22
18
  import { applyHfEnvOverrides, isHfDownloadFailure, withHfDownloadRetry } from './hf-env.js';
23
19
  import { getLocalEmbeddingRuntimeBlocker } from './runtime-support.js';
24
20
  import { ensureOnnxRuntimeCommonResolvable } from './onnxruntime-common-resolver.js';
21
+ import { ensureOnnxRuntimeNodeMatchesSystem, isEffectiveCudaAvailable, } from './onnxruntime-node-resolver.js';
25
22
  import { logger } from '../logger.js';
26
- /**
27
- * Check whether the onnxruntime-node package that @huggingface/transformers
28
- * will actually load at runtime ships the CUDA execution provider.
29
- *
30
- * Critical: we resolve from transformers' own module scope, NOT from ours.
31
- * npm may install two copies — a top-level 1.24.x (our dep) and a nested
32
- * 1.21.0 (transformers' pinned dep). The guard must inspect whichever copy
33
- * transformers.js will dlopen, otherwise the check is meaningless.
34
- */
35
- function hasOrtCudaProvider() {
36
- try {
37
- const require = createRequire(import.meta.url);
38
- // Resolve from @huggingface/transformers' scope so we find the same
39
- // onnxruntime-node binary that transformers.js will use at runtime
40
- const transformersDir = dirname(require.resolve('@huggingface/transformers/package.json'));
41
- const ortRequire = createRequire(join(transformersDir, 'package.json'));
42
- const ortPath = dirname(ortRequire.resolve('onnxruntime-node/package.json'));
43
- // ORT 1.24.x only ships CUDA binaries for linux/x64 (downloaded from NuGet
44
- // at postinstall). arm64 will correctly return false here until ORT adds support.
45
- const arch = process.arch;
46
- return existsSync(join(ortPath, 'bin', 'napi-v6', 'linux', arch, 'libonnxruntime_providers_cuda.so'));
47
- }
48
- catch {
49
- return false;
50
- }
51
- }
52
- /**
53
- * Check whether CUDA libraries are actually available on this system.
54
- * ONNX Runtime's native layer crashes (uncatchable) if we attempt CUDA
55
- * without the required shared libraries, so we probe first.
56
- *
57
- * Checks both:
58
- * 1. That system CUDA libraries (libcublasLt) are present
59
- * 2. That onnxruntime-node ships the CUDA execution provider binary
60
- *
61
- * Both conditions must be true — system CUDA libs alone are not enough
62
- * if onnxruntime-node is a CPU-only build (versions < 1.24.0).
63
- */
64
- function isCudaAvailable() {
65
- // First, verify onnxruntime-node has the CUDA provider binary.
66
- // Without this, requesting CUDA causes an uncatchable native crash.
67
- if (!hasOrtCudaProvider())
68
- return false;
69
- // Primary: query the dynamic linker cache — covers all architectures,
70
- // distro layouts, and custom install paths registered with ldconfig
71
- try {
72
- const out = execFileSync('ldconfig', ['-p'], {
73
- timeout: 3000,
74
- encoding: 'utf-8',
75
- windowsHide: true,
76
- });
77
- if (out.includes('libcublasLt.so.12'))
78
- return true;
79
- }
80
- catch {
81
- // ldconfig not available (e.g. non-standard container)
82
- }
83
- // Fallback: check CUDA_PATH and LD_LIBRARY_PATH for environments where
84
- // ldconfig doesn't know about the CUDA install (conda, manual /opt/cuda, etc.)
85
- for (const envVar of ['CUDA_PATH', 'LD_LIBRARY_PATH']) {
86
- const val = process.env[envVar];
87
- if (!val)
88
- continue;
89
- for (const dir of val.split(':').filter(Boolean)) {
90
- if (existsSync(join(dir, 'lib64', 'libcublasLt.so.12')) ||
91
- existsSync(join(dir, 'lib', 'libcublasLt.so.12')) ||
92
- existsSync(join(dir, 'libcublasLt.so.12')))
93
- return true;
94
- }
95
- }
96
- return false;
97
- }
98
23
  // Module-level state for singleton pattern
99
24
  let embedderInstance = null;
100
25
  let isInitializing = false;
@@ -142,7 +67,7 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
142
67
  // provider libraries are missing. DirectML stays opt-in for the same reason.
143
68
  // Probe for CUDA first — ONNX Runtime crashes (uncatchable native error)
144
69
  // if we attempt CUDA without the required shared libraries
145
- const gpuDevice = isCudaAvailable() ? 'cuda' : 'cpu';
70
+ const gpuDevice = isEffectiveCudaAvailable() ? 'cuda' : 'cpu';
146
71
  const requestedDevice = forceDevice || (finalConfig.device === 'auto' ? gpuDevice : finalConfig.device);
147
72
  initPromise = (async () => {
148
73
  try {
@@ -151,6 +76,12 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
151
76
  // Under pnpm-strict / `pnpm dlx`, transformers' phantom `onnxruntime-common`
152
77
  // import is unresolvable; register the fallback resolver first (#307).
153
78
  ensureOnnxRuntimeCommonResolvable();
79
+ // Registered AFTER the common fallback so this hook resolves FIRST (Node
80
+ // runs the most-recently-registered hook first): on CUDA-13 hosts it
81
+ // redirects onnxruntime-node (and its version-matched onnxruntime-common)
82
+ // to the CUDA-13 build before transformers imports them. No-op on matching
83
+ // layouts, non-CUDA, Windows/DirectML, and macOS.
84
+ ensureOnnxRuntimeNodeMatchesSystem();
154
85
  const { pipeline, env } = await import('@huggingface/transformers');
155
86
  // Configure transformers.js environment
156
87
  env.allowLocalModels = false;
@@ -21,14 +21,18 @@
21
21
  * Install a synchronous, in-thread ESM resolution hook (`module.registerHooks`,
22
22
  * Node >= 22.15) that redirects `onnxruntime-common` to a copy gitnexus can
23
23
  * resolve — but only when the default resolver fails. The redirect target is
24
- * preferentially the `onnxruntime-common` that `onnxruntime-node` (the native
25
- * binding transformers actually loads) itself depends on, so the redirected copy
26
- * is version-matched to that binding even under `pnpm dlx` — where gitnexus'
27
- * npm-style `overrides` block does NOT apply, because it is honoured only from a
28
- * root manifest and gitnexus is a transitive dependency there. It falls back to
29
- * gitnexus' own direct `onnxruntime-common` dependency when that chain can't be
30
- * walked. onnxruntime-common is a stable, pure-JS package whose `Tensor` surface
31
- * is unchanged across 1.24–1.26, so either target is API-compatible. On working
24
+ * preferentially the `onnxruntime-common` that `onnxruntime-node` depends on —
25
+ * specifically {@link getEffectiveOnnxRuntimeNodeDir}, the SAME onnxruntime-node
26
+ * copy the sibling {@link ./onnxruntime-node-resolver.ts} CUDA-major redirect
27
+ * will actually load (transformers' own default when no redirect is active,
28
+ * or the CUDA-build-matched copy when one is) so this hook and that one can
29
+ * never disagree about which onnxruntime-node's own onnxruntime-common
30
+ * dependency to pair with, even under `pnpm dlx` where gitnexus' npm-style
31
+ * `overrides` block does NOT apply (honoured only from a root manifest, and
32
+ * gitnexus is a transitive dependency there). Falls back to gitnexus' own
33
+ * direct `onnxruntime-common` dependency when that chain can't be walked.
34
+ * onnxruntime-common is a stable, pure-JS package whose `Tensor` surface is
35
+ * unchanged across 1.24–1.26, so either target is API-compatible. On working
32
36
  * layouts the default resolver succeeds first and the hook never fires, so
33
37
  * behaviour is unchanged.
34
38
  *
@@ -55,26 +59,27 @@
55
59
  */
56
60
  import { registerHooks, createRequire } from 'node:module';
57
61
  import { pathToFileURL } from 'node:url';
62
+ import { join } from 'node:path';
63
+ import { getEffectiveOnnxRuntimeNodeDir } from './onnxruntime-node-resolver.js';
58
64
  import { logger } from '../logger.js';
59
65
  let attempted = false;
60
66
  /**
61
67
  * Compute the file: URL the hook redirects `onnxruntime-common` to.
62
68
  *
63
- * Prefer the copy `onnxruntime-node` (the native binding transformers loads)
64
- * depends on, so the redirected module is version-matched to the binding even
65
- * under `pnpm dlx`, where transformers keeps its own pinned onnxruntime-node.
66
- * The walk resolves transformers' MAIN entry NOT `@huggingface/transformers/
67
- * package.json`, which transformers' `exports` map blocks
68
- * (`ERR_PACKAGE_PATH_NOT_EXPORTED`) then onnxruntime-node, then its
69
- * onnxruntime-common. Falls back to gitnexus' own direct dependency (always
70
- * resolvable from our scope) when any step fails.
69
+ * Pair with {@link getEffectiveOnnxRuntimeNodeDir}'s onnxruntime-node copy
70
+ * NOT independently re-derived — so the redirected module is version-matched
71
+ * to whichever onnxruntime-node will actually load, even under `pnpm dlx`
72
+ * (where transformers keeps its own pinned onnxruntime-node) and even when
73
+ * the sibling CUDA-major redirect is active. Falls back to gitnexus' own
74
+ * direct dependency (always resolvable from our scope) when that fails.
71
75
  */
72
76
  const resolveOnnxRuntimeCommonUrl = () => {
73
77
  const require = createRequire(import.meta.url);
74
78
  try {
75
- const transformersMain = require.resolve('@huggingface/transformers');
76
- const ortNodePkg = createRequire(transformersMain).resolve('onnxruntime-node/package.json');
77
- const common = createRequire(ortNodePkg).resolve('onnxruntime-common');
79
+ const effectiveDir = getEffectiveOnnxRuntimeNodeDir();
80
+ if (!effectiveDir)
81
+ throw new Error('no effective onnxruntime-node dir resolved');
82
+ const common = createRequire(join(effectiveDir, 'package.json')).resolve('onnxruntime-common');
78
83
  return pathToFileURL(common).href;
79
84
  }
80
85
  catch {
@@ -0,0 +1,40 @@
1
+ export type CudaMajor = 12 | 13;
2
+ /** The CUDA major an onnxruntime-node copy's CUDA provider links against, or null (Linux/x64 only ships one). */
3
+ export declare const ortCudaMajor: (ortNodeDir: string) => CudaMajor | null;
4
+ /** The cuBLASLt major installed on this system, or null. Linux only. */
5
+ export declare const detectSystemCudaMajor: () => CudaMajor | null;
6
+ /**
7
+ * The onnxruntime-node dir that will actually back transformers at runtime once
8
+ * {@link ensureOnnxRuntimeNodeMatchesSystem} has run — i.e. the redirected copy
9
+ * when a redirect applies, otherwise transformers' default. The CUDA probe must
10
+ * inspect THIS dir (not transformers' CJS-resolved default) so probe and
11
+ * runtime agree. Returns null only when neither copy resolves.
12
+ */
13
+ export declare const getEffectiveOnnxRuntimeNodeDir: () => string | null;
14
+ /**
15
+ * Whether the onnxruntime-node copy that will actually load ships a CUDA
16
+ * provider matching this host's CUDA major — reads straight from the cached
17
+ * `decide()` result rather than re-probing `ortCudaMajor`/`detectSystemCudaMajor`
18
+ * a second time (both are already computed above). `systemMajor` is checked
19
+ * for non-null explicitly so two absent majors (null === null) never count
20
+ * as a match.
21
+ */
22
+ export declare const isEffectiveCudaAvailable: () => boolean;
23
+ /**
24
+ * CUDA-build-redirect status for the `doctor` Embeddings section — pure
25
+ * summary of decide()'s already-computed decision, matching
26
+ * doctor.ts's `localEmbeddingDoctorStatus`'s `{status, detail}` shape so an
27
+ * operator can tell "why is my CUDA-13 host still on CPU" apart from
28
+ * "there's no system CUDA to redirect for" at a glance.
29
+ */
30
+ export declare const cudaRedirectDoctorStatus: () => {
31
+ status: string;
32
+ detail: string | null;
33
+ };
34
+ /**
35
+ * Idempotently install the CUDA-build-matching redirect. Call once immediately
36
+ * before the dynamic `import('@huggingface/transformers')` on the local
37
+ * embedding path (after the runtime guard, alongside the onnxruntime-common
38
+ * fallback). No-op unless a strictly-better matching copy exists.
39
+ */
40
+ export declare const ensureOnnxRuntimeNodeMatchesSystem: () => void;
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Redirect `@huggingface/transformers`' `onnxruntime-node` import to whichever
3
+ * bundled copy's CUDA build matches this host's CUDA runtime (CUDA 12 vs 13).
4
+ *
5
+ * ## Why
6
+ * transformers exact-pins `onnxruntime-node` (e.g. `1.24.3`, a CUDA **12**
7
+ * build), while gitnexus' own `onnxruntime-node: ^1.24.0` floats to the latest
8
+ * 1.x (a CUDA **13** build). npm/pnpm cannot dedupe an exact pin against a
9
+ * range, so a `npm i -g` install ends up with TWO copies: gitnexus' top-level
10
+ * CUDA-13 build (unused) and transformers' nested CUDA-12 build (the one that
11
+ * actually loads). gitnexus' `overrides` block that would collapse them is
12
+ * honoured only from a *root* manifest, so it is inert once gitnexus is a
13
+ * dependency — the same transitive-override limitation documented in
14
+ * {@link ./onnxruntime-common-resolver.ts} (#307).
15
+ *
16
+ * The consequence on a CUDA-13-only host: the nested CUDA-12 provider cannot
17
+ * find `libcublasLt.so.12`, the CUDA execution provider fails to load, and
18
+ * embeddings silently fall back to CPU (~5-6x slower) even with
19
+ * `--embedding-device cuda`.
20
+ *
21
+ * ## What this does
22
+ * Best-effort, before transformers is imported: if the system's cuBLASLt major
23
+ * (12 or 13) does NOT match the CUDA build transformers would load by default,
24
+ * but gitnexus' own top-level `onnxruntime-node` copy DOES match, install a
25
+ * synchronous ESM resolution hook (`module.registerHooks`, Node >= 22.15) that
26
+ * redirects both `onnxruntime-node` and `onnxruntime-common` to that matching
27
+ * copy. onnxruntime-common is redirected alongside so the `Tensor` surface
28
+ * stays a single identity, version-matched to the redirected binding.
29
+ *
30
+ * ## Safety
31
+ * Detection-based and conservative — it acts ONLY when it is a net improvement:
32
+ * - system CUDA major == default build major -> NO-OP (already correct)
33
+ * - no system CUDA libs / non-linux -> NO-OP (CPU path)
34
+ * - only one copy present -> NO-OP
35
+ * - neither copy matches the system -> NO-OP (never makes it worse)
36
+ * So CUDA-12 hosts, Windows (DirectML), macOS, and CPU-only hosts are
37
+ * untouched. Idempotent; any failure is swallowed and leaves the default
38
+ * resolution exactly as before. `module.registerHooks` requires Node >= 22.15
39
+ * (the gitnexus engines floor is >= 22.0.0); on older runtimes the redirect is
40
+ * a no-op, but the default copy's CUDA major is still probed so an
41
+ * already-matching host (e.g. CUDA 12 + transformers' CUDA-12 build) keeps
42
+ * auto-selecting the GPU.
43
+ * `npm link` / symlinked local-dev checkouts are a known caveat: `resolveOurOrtNodeDir`/
44
+ * `resolveDefaultOrtNodeDir` are anchored to this module's own real (post-symlink)
45
+ * location via `import.meta.url`, so a linked dev checkout may resolve against
46
+ * its own `node_modules` rather than the consuming app's — narrow, dev-only
47
+ * blast radius; regular npm/pnpm installs are unaffected.
48
+ *
49
+ * The CUDA-major decision is exposed via {@link getEffectiveOnnxRuntimeNodeDir}
50
+ * so the embedder's CUDA probe can inspect the SAME copy that will actually be
51
+ * loaded (the probe uses CJS `require.resolve`, which an ESM hook does not
52
+ * affect) — keeping probe and runtime consistent.
53
+ */
54
+ import { registerHooks, createRequire } from 'node:module';
55
+ import { pathToFileURL } from 'node:url';
56
+ import { existsSync } from 'node:fs';
57
+ import { join, dirname } from 'node:path';
58
+ import { execFileSync } from 'node:child_process';
59
+ import { logger } from '../logger.js';
60
+ const require = createRequire(import.meta.url);
61
+ /**
62
+ * Read a shared object's NEEDED entries, tolerating ldd's non-zero exit when a
63
+ * lib is unresolved (that case still yields a usable "=> not found" stdout).
64
+ * `failed: true` means ldd produced no usable output at all (missing `ldd`
65
+ * binary, permission-denied `.so`, sandboxed exec) — distinct from "ldd ran
66
+ * fine and simply found no matching NEEDED entry" (`failed: false`, `needed: ''`),
67
+ * so callers don't have to treat "detection failed" identically to "definitely
68
+ * no CUDA provider".
69
+ */
70
+ const readSoNeeded = (soPath) => {
71
+ try {
72
+ return {
73
+ needed: execFileSync('ldd', [soPath], {
74
+ timeout: 5000,
75
+ encoding: 'utf-8',
76
+ windowsHide: true,
77
+ }),
78
+ failed: false,
79
+ };
80
+ }
81
+ catch (err) {
82
+ const out = err?.stdout;
83
+ if (typeof out === 'string' && out.length > 0)
84
+ return { needed: out, failed: false };
85
+ return { needed: '', failed: true };
86
+ }
87
+ };
88
+ /** The CUDA major an onnxruntime-node copy's CUDA provider links against, or null (Linux/x64 only ships one). */
89
+ export const ortCudaMajor = (ortNodeDir) => {
90
+ const so = join(ortNodeDir, 'bin', 'napi-v6', 'linux', process.arch, 'libonnxruntime_providers_cuda.so');
91
+ // A pre-PR CUDA-12 host relied only on this existence check (no `ldd`
92
+ // dependency) — retained here as the first, unconditional signal so a host
93
+ // whose CUDA provider `.so` is genuinely present but merely un-inspectable
94
+ // (see the `failed` case below) is never treated identically to a host that
95
+ // never shipped a CUDA provider at all.
96
+ if (!existsSync(so))
97
+ return null;
98
+ const { needed, failed } = readSoNeeded(so);
99
+ if (failed) {
100
+ logger.warn({ so }, 'Could not read CUDA provider dependencies (ldd failed to run) — CUDA-major detection ' +
101
+ 'is unknown, not necessarily absent; embeddings will fall back to CPU either way');
102
+ }
103
+ if (/libcublasLt\.so\.13/.test(needed))
104
+ return 13;
105
+ if (/libcublasLt\.so\.12/.test(needed))
106
+ return 12;
107
+ return null;
108
+ };
109
+ /** The cuBLASLt major installed on this system, or null. Linux only. */
110
+ export const detectSystemCudaMajor = () => {
111
+ if (process.platform !== 'linux')
112
+ return null;
113
+ try {
114
+ const out = execFileSync('ldconfig', ['-p'], {
115
+ timeout: 3000,
116
+ encoding: 'utf-8',
117
+ windowsHide: true,
118
+ });
119
+ if (out.includes('libcublasLt.so.13'))
120
+ return 13;
121
+ if (out.includes('libcublasLt.so.12'))
122
+ return 12;
123
+ }
124
+ catch {
125
+ // ldconfig not available (e.g. non-standard container) — fall through to path scan.
126
+ }
127
+ // Prefer CUDA 13 across the ENTIRE search space, not just within one
128
+ // dir/sub pair — a `.so.12` found early (e.g. a stale CUDA_PATH entry from
129
+ // a prior install) must not shadow a genuine `.so.13` found later in
130
+ // LD_LIBRARY_PATH. Return immediately on a 13 (the best possible answer);
131
+ // remember a 12 and keep scanning in case a later entry still has a 13.
132
+ let found = null;
133
+ for (const envVar of ['CUDA_PATH', 'LD_LIBRARY_PATH']) {
134
+ const val = process.env[envVar];
135
+ if (!val)
136
+ continue;
137
+ for (const dir of val.split(':').filter(Boolean))
138
+ for (const sub of ['lib64', 'lib', ''])
139
+ for (const maj of [13, 12])
140
+ if (existsSync(join(dir, sub, `libcublasLt.so.${maj}`))) {
141
+ if (maj === 13)
142
+ return 13;
143
+ found = maj;
144
+ }
145
+ }
146
+ return found;
147
+ };
148
+ /** onnxruntime-node dir transformers loads by default (its own nested/pinned copy). */
149
+ const resolveDefaultOrtNodeDir = () => {
150
+ try {
151
+ const transformersMain = require.resolve('@huggingface/transformers');
152
+ return dirname(createRequire(transformersMain).resolve('onnxruntime-node/package.json'));
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ };
158
+ /** gitnexus' own direct top-level onnxruntime-node dir. */
159
+ const resolveOurOrtNodeDir = () => {
160
+ try {
161
+ return dirname(require.resolve('onnxruntime-node/package.json'));
162
+ }
163
+ catch {
164
+ return null;
165
+ }
166
+ };
167
+ let cached = null;
168
+ const decide = () => {
169
+ if (cached)
170
+ return cached;
171
+ const defaultDir = resolveDefaultOrtNodeDir();
172
+ // Node < 22.15 has no `registerHooks` API, so a redirect can never actually
173
+ // install (see ensureOnnxRuntimeNodeMatchesSystem below) — the probe must
174
+ // agree with that up front, never reporting a redirect target that won't be
175
+ // loaded. But the DEFAULT copy still loads and needs no hook, so its CUDA
176
+ // major is still probed: a CUDA-12 host on Node 22.0–22.14 whose default
177
+ // build already matches must keep auto-selecting the GPU exactly as it did
178
+ // before this redirect existed.
179
+ const canRedirect = typeof registerHooks === 'function';
180
+ const systemMajor = detectSystemCudaMajor();
181
+ // `defaultDir` resolving is NOT a precondition for checking `ourDir` below —
182
+ // if transformers' own resolution fails outright (defaultMajor stays null),
183
+ // that still counts as "the default doesn't match", so a working `ourDir`
184
+ // should still be picked up as the effective target instead of leaving
185
+ // `effectiveDir` stuck at `null`. Gated behind `systemMajor != null` (as
186
+ // before) so a non-CUDA host never pays for a provider-.so probe at all.
187
+ const defaultMajor = systemMajor != null && defaultDir ? ortCudaMajor(defaultDir) : null;
188
+ let decision = {
189
+ redirect: false,
190
+ effectiveDir: defaultDir,
191
+ effectiveMajor: defaultMajor,
192
+ systemMajor,
193
+ };
194
+ if (canRedirect && systemMajor != null && defaultMajor !== systemMajor) {
195
+ const ourDir = resolveOurOrtNodeDir();
196
+ if (ourDir && ourDir !== defaultDir) {
197
+ const ourMajor = ortCudaMajor(ourDir);
198
+ if (ourMajor === systemMajor) {
199
+ decision = { redirect: true, effectiveDir: ourDir, effectiveMajor: ourMajor, systemMajor };
200
+ }
201
+ }
202
+ }
203
+ cached = decision;
204
+ return decision;
205
+ };
206
+ /**
207
+ * The onnxruntime-node dir that will actually back transformers at runtime once
208
+ * {@link ensureOnnxRuntimeNodeMatchesSystem} has run — i.e. the redirected copy
209
+ * when a redirect applies, otherwise transformers' default. The CUDA probe must
210
+ * inspect THIS dir (not transformers' CJS-resolved default) so probe and
211
+ * runtime agree. Returns null only when neither copy resolves.
212
+ */
213
+ export const getEffectiveOnnxRuntimeNodeDir = () => decide().effectiveDir;
214
+ /**
215
+ * Whether the onnxruntime-node copy that will actually load ships a CUDA
216
+ * provider matching this host's CUDA major — reads straight from the cached
217
+ * `decide()` result rather than re-probing `ortCudaMajor`/`detectSystemCudaMajor`
218
+ * a second time (both are already computed above). `systemMajor` is checked
219
+ * for non-null explicitly so two absent majors (null === null) never count
220
+ * as a match.
221
+ */
222
+ export const isEffectiveCudaAvailable = () => {
223
+ const d = decide();
224
+ return d.systemMajor !== null && d.systemMajor === d.effectiveMajor;
225
+ };
226
+ /**
227
+ * CUDA-build-redirect status for the `doctor` Embeddings section — pure
228
+ * summary of decide()'s already-computed decision, matching
229
+ * doctor.ts's `localEmbeddingDoctorStatus`'s `{status, detail}` shape so an
230
+ * operator can tell "why is my CUDA-13 host still on CPU" apart from
231
+ * "there's no system CUDA to redirect for" at a glance.
232
+ */
233
+ export const cudaRedirectDoctorStatus = () => {
234
+ const d = decide();
235
+ if (d.systemMajor === null) {
236
+ return { status: 'n/a (no system CUDA detected)', detail: null };
237
+ }
238
+ if (d.redirect) {
239
+ return {
240
+ status: `✓ redirected onnxruntime-node to the CUDA ${d.systemMajor} build`,
241
+ detail: d.effectiveDir,
242
+ };
243
+ }
244
+ if (d.systemMajor === d.effectiveMajor) {
245
+ return {
246
+ status: `✓ default onnxruntime-node build already matches CUDA ${d.systemMajor}`,
247
+ detail: null,
248
+ };
249
+ }
250
+ return {
251
+ status: `✗ no CUDA ${d.systemMajor}-matched onnxruntime-node build found (falling back to CPU)`,
252
+ detail: d.effectiveDir,
253
+ };
254
+ };
255
+ let attempted = false;
256
+ /**
257
+ * Idempotently install the CUDA-build-matching redirect. Call once immediately
258
+ * before the dynamic `import('@huggingface/transformers')` on the local
259
+ * embedding path (after the runtime guard, alongside the onnxruntime-common
260
+ * fallback). No-op unless a strictly-better matching copy exists.
261
+ */
262
+ export const ensureOnnxRuntimeNodeMatchesSystem = () => {
263
+ if (attempted)
264
+ return;
265
+ attempted = true;
266
+ try {
267
+ if (typeof registerHooks !== 'function')
268
+ return; // Node < 22.15: graceful no-op
269
+ const d = decide();
270
+ if (!d.redirect || !d.effectiveDir)
271
+ return;
272
+ const nodeUrl = pathToFileURL(createRequire(join(d.effectiveDir, 'package.json')).resolve('onnxruntime-node')).href;
273
+ let commonUrl = null;
274
+ try {
275
+ commonUrl = pathToFileURL(createRequire(join(d.effectiveDir, 'package.json')).resolve('onnxruntime-common')).href;
276
+ }
277
+ catch {
278
+ commonUrl = null; // fall back to the onnxruntime-common-resolver for common
279
+ }
280
+ registerHooks({
281
+ resolve(specifier, context, nextResolve) {
282
+ if (specifier === 'onnxruntime-node')
283
+ return { url: nodeUrl, shortCircuit: true };
284
+ if (commonUrl && specifier === 'onnxruntime-common')
285
+ return { url: commonUrl, shortCircuit: true };
286
+ return nextResolve(specifier, context);
287
+ },
288
+ });
289
+ // info (not debug): this is the one signal an operator has that CUDA
290
+ // embeddings are actually using the GPU on this host — the common/no-op
291
+ // paths below stay at debug since they're the expected default.
292
+ logger.info({ systemMajor: d.systemMajor, effectiveDir: d.effectiveDir }, 'Redirected onnxruntime-node to system-matched CUDA build');
293
+ }
294
+ catch (err) {
295
+ logger.debug({ err: err instanceof Error ? err.message : String(err) }, 'onnxruntime-node CUDA-build redirect not installed');
296
+ }
297
+ };
@@ -9,6 +9,7 @@ import { resolveEmbeddingConfig } from '../../core/embeddings/config.js';
9
9
  import { applyHfEnvOverrides, isHfDownloadFailure, withHfDownloadRetry, } from '../../core/embeddings/hf-env.js';
10
10
  import { getLocalEmbeddingRuntimeBlocker } from '../../core/embeddings/runtime-support.js';
11
11
  import { ensureOnnxRuntimeCommonResolvable } from '../../core/embeddings/onnxruntime-common-resolver.js';
12
+ import { ensureOnnxRuntimeNodeMatchesSystem } from '../../core/embeddings/onnxruntime-node-resolver.js';
12
13
  import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/lbug/pool-adapter.js';
13
14
  import { logger } from '../../core/logger.js';
14
15
  // Model config
@@ -47,6 +48,13 @@ export const initEmbedder = async () => {
47
48
  // Under pnpm-strict / `pnpm dlx`, transformers' phantom `onnxruntime-common`
48
49
  // import is unresolvable; register the fallback resolver first (#307).
49
50
  ensureOnnxRuntimeCommonResolvable();
51
+ // Registered AFTER the common fallback so this hook resolves FIRST (Node
52
+ // runs the most-recently-registered hook first): on CUDA-13 hosts it
53
+ // redirects onnxruntime-node to the system-matched build before
54
+ // transformers imports it. No-op on matching layouts, non-CUDA,
55
+ // Windows/DirectML, and macOS. Mirrors the core embedder's call site so
56
+ // MCP query-time embedding gets the same CUDA-13 fix.
57
+ ensureOnnxRuntimeNodeMatchesSystem();
50
58
  const { pipeline, env } = await import('@huggingface/transformers');
51
59
  env.allowLocalModels = false;
52
60
  // Bridge user-controlled env vars to transformers.js: HF_HOME →
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.9-rc.40",
3
+ "version": "1.6.9-rc.41",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -47,6 +47,7 @@ const PLATFORM_LOGIC = [
47
47
  'test/unit/ignore-service.test.ts',
48
48
  'test/unit/group/bridge-db.test.ts',
49
49
  'test/unit/group/bridge-db-edge.test.ts',
50
+ 'test/unit/onnxruntime-node-resolver.test.ts',
50
51
  ];
51
52
 
52
53
  // Native LadybugDB integration tests — exercise the @ladybugdb/core