From 343b67da5e9bc93f08ff48b28bbe06efee93b668 Mon Sep 17 00:00:00 2001 From: Pieter Pel Date: Tue, 12 May 2026 10:32:18 +0200 Subject: [PATCH] feat(cli): add system onnxruntime-node binding override --- gitnexus/README.md | 9 ++++ gitnexus/src/core/embeddings/embedder.ts | 2 + .../embeddings/onnxruntime-node-loader.ts | 53 +++++++++++++++++++ gitnexus/src/mcp/core/embedder.ts | 2 + 4 files changed, 66 insertions(+) create mode 100644 gitnexus/src/core/embeddings/onnxruntime-node-loader.ts diff --git a/gitnexus/README.md b/gitnexus/README.md index 55cdc12f7..e75d62b52 100644 --- a/gitnexus/README.md +++ b/gitnexus/README.md @@ -192,6 +192,15 @@ gitnexus analyze . --embeddings Works with Infinity, vLLM, TEI, llama.cpp, Ollama, LM Studio, or OpenAI. When unset, local embeddings are used unchanged. +## System ONNX Runtime Binding (Advanced) + +If your environment provides a system-built onnxruntime-node binding (for example, Nix), +you can direct GitNexus to use it instead of the package's bundled binary: + +```bash +export GITNEXUS_ORT_BINDING_PATH=/absolute/path/to/onnxruntime_binding.node +``` + ## Multi-Repo Support GitNexus supports indexing multiple repositories. Each `gitnexus analyze` registers the repo in a global registry (`~/.gitnexus/registry.json`). The MCP server serves all indexed repos automatically. diff --git a/gitnexus/src/core/embeddings/embedder.ts b/gitnexus/src/core/embeddings/embedder.ts index 72ddcbd70..791144076 100644 --- a/gitnexus/src/core/embeddings/embedder.ts +++ b/gitnexus/src/core/embeddings/embedder.ts @@ -28,6 +28,7 @@ import { DEFAULT_EMBEDDING_CONFIG, type EmbeddingConfig, type ModelProgress } fr import { isHttpMode, getHttpDimensions, httpEmbed } from './http-client.js'; import { resolveEmbeddingConfig } from './config.js'; import { applyHfEnvOverrides, isHfDownloadFailure, withHfDownloadRetry } from './hf-env.js'; +import { applyOnnxruntimeNodeBindingOverride } from './onnxruntime-node-loader.js'; import { logger } from '../logger.js'; /** @@ -164,6 +165,7 @@ export const initEmbedder = async ( try { // Configure transformers.js environment env.allowLocalModels = false; + applyOnnxruntimeNodeBindingOverride(); // Bridge user-controlled env vars to transformers.js: HF_HOME → // env.cacheDir, HF_ENDPOINT → env.remoteHost (#1205). Centralised in // applyHfEnvOverrides so the MCP embedder entry point behaves diff --git a/gitnexus/src/core/embeddings/onnxruntime-node-loader.ts b/gitnexus/src/core/embeddings/onnxruntime-node-loader.ts new file mode 100644 index 000000000..252064f3d --- /dev/null +++ b/gitnexus/src/core/embeddings/onnxruntime-node-loader.ts @@ -0,0 +1,53 @@ +import Module from 'module'; +import { existsSync } from 'fs'; +import { isAbsolute } from 'path'; + +const OVERRIDE_ENV = 'GITNEXUS_ORT_BINDING_PATH'; + +/** + * Redirect onnxruntime-node to a system-provided binding binary. + * + * onnxruntime-node always requires a native .node file from its own package. + * We patch Node's module resolver so that specific request is redirected to + * the path provided by GITNEXUS_ORT_BINDING_PATH. + */ +export const applyOnnxruntimeNodeBindingOverride = (): void => { + const overridePath = process.env[OVERRIDE_ENV]; + if (!overridePath) return; + + if (!isAbsolute(overridePath)) { + throw new Error(`${OVERRIDE_ENV} must be an absolute path, got "${overridePath}"`); + } + + if (!existsSync(overridePath)) { + throw new Error(`${OVERRIDE_ENV} points to a missing file: "${overridePath}"`); + } + + const moduleAny = Module as typeof Module & { + _gitnexusOrtBindingOverrideApplied?: boolean; + _resolveFilename?: ( + request: string, + parent: NodeModule | null, + isMain: boolean, + options?: unknown + ) => string; + }; + + if (moduleAny._gitnexusOrtBindingOverrideApplied) return; + moduleAny._gitnexusOrtBindingOverrideApplied = true; + + const originalResolve = moduleAny._resolveFilename?.bind(Module); + if (!originalResolve) return; + + moduleAny._resolveFilename = function ( + request: string, + parent: NodeModule | null, + isMain: boolean, + options?: unknown + ): string { + if (typeof request === 'string' && request.endsWith('onnxruntime_binding.node')) { + return overridePath; + } + return originalResolve(request, parent, isMain, options); + }; +}; diff --git a/gitnexus/src/mcp/core/embedder.ts b/gitnexus/src/mcp/core/embedder.ts index f529f2805..58da2cf7e 100644 --- a/gitnexus/src/mcp/core/embedder.ts +++ b/gitnexus/src/mcp/core/embedder.ts @@ -17,6 +17,7 @@ import { isHfDownloadFailure, withHfDownloadRetry, } from '../../core/embeddings/hf-env.js'; +import { applyOnnxruntimeNodeBindingOverride } from '../../core/embeddings/onnxruntime-node-loader.js'; import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/lbug/pool-adapter.js'; import { logger } from '../../core/logger.js'; @@ -49,6 +50,7 @@ export const initEmbedder = async (): Promise => { initPromise = (async () => { try { env.allowLocalModels = false; + applyOnnxruntimeNodeBindingOverride(); // Bridge user-controlled env vars to transformers.js: HF_HOME → // env.cacheDir, HF_ENDPOINT → env.remoteHost (#1205). Centralised in // applyHfEnvOverrides so this MCP entry point behaves identically to