merged PR #5432: fix(mnemopi): pin Windows ORT DLL path

This commit is contained in:
can1357
2026-07-16 03:32:02 +02:00
3 changed files with 102 additions and 15 deletions
+1
View File
@@ -5,6 +5,7 @@
### Fixed
- Fixed working-memory TTL trim silently deleting restored or imported durable rows: rows keeping `consolidated_at = NULL` with an old `timestamp` are no longer trimmed when flagged `IMPORTED`, `importFromDict` stamps imported rows as consolidated, and every working-memory delete path (trim, `forgetWorking`, force-import overwrite) now cascades linked annotations, embeddings, facts, memoria projections, gists, and graph edges instead of leaving orphans. ([#4819](https://github.com/can1357/oh-my-pi/issues/4819))
- Fixed Mnemopi local embeddings on Windows loading an unrelated `onnxruntime.dll` from the inherited system path instead of fastembed's cached ORT runtime. ([#4849](https://github.com/can1357/oh-my-pi/issues/4849))
## [16.3.9] - 2026-07-06
+78 -14
View File
@@ -48,6 +48,67 @@ export function fastembedRuntimeInstallPlan(): FastembedRuntimeInstallPlan {
}
let fastembedLoad: Promise<FastembedModule> | null = null;
/** Inputs for selecting the Windows DLL directory paired with a fastembed installation. */
export interface WindowsFastembedRuntimeOptions {
/** Resolved fastembed package entry whose dependency graph owns the ORT binding. */
fastembedEntry: string;
/** Directory containing fastembed's manifest and nested dependency graph. */
fastembedPackageDir: string;
/** Native architecture to select; defaults to the current process architecture. */
arch?: string;
/** Environment receiving the DLL search path; defaults to the subprocess environment. */
env?: NodeJS.ProcessEnv;
}
/** The ORT module and DLL directory selected from fastembed's own dependency graph. */
export interface WindowsFastembedRuntime {
/** Resolved entry for fastembed's own ONNX Runtime dependency. */
ortEntry: string;
/** Package directory containing the selected ORT manifest and native assets. */
ortPackageDir: string;
/** Directory prepended to `PATH` so Windows finds the paired native DLL. */
dllDir: string;
}
/**
* Prepend the ORT DLL directory paired with fastembed before Bun loads its
* native binding. Compiled Windows binaries extract `.node` files to a
* temporary directory, so the default DLL search can otherwise select an
* unrelated `onnxruntime.dll` from the inherited system path.
*/
export async function prepareWindowsFastembedRuntime({
fastembedEntry,
fastembedPackageDir,
arch = process.arch,
env = process.env,
}: WindowsFastembedRuntimeOptions): Promise<WindowsFastembedRuntime> {
const nestedNodeModules = path.join(fastembedPackageDir, "node_modules");
const rootNodeModules = path.dirname(fastembedPackageDir);
const nestedOrtEntry = resolveRuntimeModule(nestedNodeModules, "onnxruntime-node");
const ortEntry = nestedOrtEntry ?? resolveRuntimeModule(rootNodeModules, "onnxruntime-node");
const ortPackageDir = path.join(nestedOrtEntry ? nestedNodeModules : rootNodeModules, "onnxruntime-node");
if (!ortEntry) {
throw new Error(`Cannot find module onnxruntime-node beside ${fastembedEntry}`);
}
const dllGlob = new Bun.Glob(`bin/napi-*/win32/${arch}/onnxruntime.dll`);
let dllDir: string | undefined;
for await (const dll of dllGlob.scan({ cwd: ortPackageDir, absolute: true, onlyFiles: true })) {
dllDir = path.dirname(dll);
break;
}
if (!dllDir) {
throw new Error(`Cannot find module onnxruntime-node Windows DLL for ${arch} beside ${ortEntry}`);
}
const currentPath = env.PATH;
const normalizedDllDir = path.resolve(dllDir).toLowerCase();
const alreadyPresent = currentPath
?.split(path.delimiter)
.some(entry => path.resolve(entry).toLowerCase() === normalizedDllDir);
if (!alreadyPresent) env.PATH = currentPath ? `${dllDir}${path.delimiter}${currentPath}` : dllDir;
return { ortEntry, ortPackageDir, dllDir };
}
export function loadFastembed(): Promise<FastembedModule> {
fastembedLoad ??= loadFastembedOnce().catch(error => {
fastembedLoad = null;
@@ -57,16 +118,14 @@ export function loadFastembed(): Promise<FastembedModule> {
}
async function loadFastembedOnce(): Promise<FastembedModule> {
// Dynamic imports: both packages are optional peers that eagerly load
// native addons and may be absent at runtime — a static import would load
// the addon at module-init and crash every consumer without the peers.
try {
// Preload the pinned ORT before fastembed's nested ORT — only on Windows,
// where loading the older binding first triggers a DLL-reuse crash.
if (process.platform === "win32") {
await import("onnxruntime-node");
const requireDirect = createRequire(import.meta.url);
const manifestPath = requireDirect.resolve("fastembed/package.json");
const manifest: { version?: unknown } = requireDirect(manifestPath);
if (manifest.version !== FASTEMBED_SPEC) {
throw new Error(`Cannot find package fastembed@${FASTEMBED_SPEC}; resolved ${String(manifest.version)}`);
}
return await import("fastembed");
return loadResolvedFastembed(requireDirect.resolve("fastembed"), path.dirname(manifestPath));
} catch (error) {
if (!isRecoverableFastembedLoadError(error)) throw error;
logger.debug("mnemopi: fastembed not loadable, using on-demand runtime install", {
@@ -76,6 +135,16 @@ async function loadFastembedOnce(): Promise<FastembedModule> {
}
}
async function loadResolvedFastembed(entry: string, fastembedPackageDir: string): Promise<FastembedModule> {
const requireFastembed = createRequire(entry);
if (process.platform === "win32") {
const { ortEntry } = await prepareWindowsFastembedRuntime({ fastembedEntry: entry, fastembedPackageDir });
requireFastembed(ortEntry);
}
const loaded: FastembedModule = requireFastembed(entry);
return loaded;
}
async function loadFromRuntimeInstall(): Promise<FastembedModule> {
const plan = fastembedRuntimeInstallPlan();
const runtimeDir = await ensureRuntimeInstalled({
@@ -89,14 +158,9 @@ async function loadFromRuntimeInstall(): Promise<FastembedModule> {
// onnxruntime-node, @anush008/tokenizers → platform binding, …) through
// the runtime cache.
installRuntimeModuleResolver({ runtimeNodeModules: nodeModules });
if (process.platform === "win32") {
const ortEntry = resolveRuntimeModule(nodeModules, "onnxruntime-node");
if (ortEntry) createRequire(ortEntry)(ortEntry);
}
const entry = resolveRuntimeModule(nodeModules, "fastembed");
if (!entry) throw new Error(`fastembed runtime install at ${runtimeDir} has no loadable entry`);
const requireRuntime = createRequire(entry);
return requireRuntime(entry) as FastembedModule;
return loadResolvedFastembed(entry, path.join(nodeModules, "fastembed"));
}
function isRecoverableFastembedLoadError(error: unknown): boolean {
@@ -1,7 +1,9 @@
import { describe, expect, test } from "bun:test";
import { createRequire } from "node:module";
import * as path from "node:path";
import rootManifest from "../../../package.json" with { type: "json" };
import packageManifest from "../package.json" with { type: "json" };
import { fastembedRuntimeInstallPlan } from "../src/core/fastembed-runtime";
import { fastembedRuntimeInstallPlan, prepareWindowsFastembedRuntime } from "../src/core/fastembed-runtime";
// The fastembed peer is pinned as an exact version (not `catalog:`) because
// `core/fastembed-runtime.ts` reads it to `bun install` the on-demand embedding
@@ -35,4 +37,24 @@ describe("fastembed runtime version pins", () => {
expect(plan.versionKey).toContain("transitive-ort");
expect(plan.versionKey).not.toContain("forced-ort");
});
test("Windows preload selects fastembed's ORT DLL before inherited paths", async () => {
const requireTest = createRequire(import.meta.url);
const fastembedManifest = requireTest.resolve("fastembed/package.json");
const fastembedEntry = requireTest.resolve("fastembed");
const inheritedPath = ["/stale-ort", "/system"].join(path.delimiter);
const env: NodeJS.ProcessEnv = { PATH: inheritedPath };
const { ortEntry, ortPackageDir, dllDir } = await prepareWindowsFastembedRuntime({
fastembedEntry,
fastembedPackageDir: path.dirname(fastembedManifest),
arch: "x64",
env,
});
const ortManifest: { version?: unknown } = requireTest(path.join(ortPackageDir, "package.json"));
expect(ortManifest.version).toBe(packageManifest.peerDependencies["onnxruntime-node"]);
expect(ortEntry.startsWith(`${ortPackageDir}${path.sep}`)).toBe(true);
expect(await Bun.file(path.join(dllDir, "onnxruntime.dll")).exists()).toBe(true);
expect(env.PATH).toBe(`${dllDir}${path.delimiter}${inheritedPath}`);
});
});