fix(mnemopi): forced fastembed onnxruntime override

- Forced the on-demand fastembed runtime manifest to override fastembed's archived onnxruntime-node 1.21.0 transitive dependency with the package's pinned 1.26.0 runtime.\n- Treated native dlopen failures from the direct fastembed import as recoverable so macOS ARM64 can fall back to the controlled runtime cache.\n- Added coverage for the runtime install plan and cache key policy.\n\nFixes #2920
This commit is contained in:
roboomp
2026-06-18 02:22:00 +00:00
parent 159b8a1c63
commit 2cb6ff38aa
3 changed files with 50 additions and 12 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Forced the on-demand fastembed runtime install to override fastembed's archived `onnxruntime-node@1.21.0` transitive pin with Mnemopi's `onnxruntime-node@1.26.0` pin, fixing local embedding startup on macOS ARM64. ([#2920](https://github.com/can1357/oh-my-pi/issues/2920))
## [16.0.6] - 2026-06-18
### Changed
+30 -11
View File
@@ -5,6 +5,7 @@ import {
getFastembedRuntimeDir,
installRuntimeModuleResolver,
logger,
type RuntimeInstallSpec,
resolveRuntimeModule,
} from "@oh-my-pi/pi-utils";
import type * as Fastembed from "fastembed";
@@ -12,13 +13,21 @@ import packageManifest from "../../package.json" with { type: "json" };
type FastembedModule = typeof Fastembed;
/** Runtime install inputs for the optional fastembed embedding stack. */
export interface FastembedRuntimeInstallPlan {
/** Cache directory key; changes when runtime resolution policy changes. */
versionKey: string;
/** Dependency graph written to the runtime cache package manifest. */
install: RuntimeInstallSpec;
}
/**
* `fastembed` and `onnxruntime-node` are optional peers (~270MB of native
* assets across platforms), never bundled and never installed eagerly. When
* the direct import cannot resolve — bundled `dist/cli.js`, compiled binary,
* or a consumer that skipped the optional peers — the pinned pair is
* `bun install`ed into a per-version runtime cache on first use and loaded
* from there (#2389).
* a consumer that skipped the optional peers, or a native loader failure from
* fastembed's nested ORT — the pinned pair is `bun install`ed into a
* per-version runtime cache on first use and loaded from there (#2389, #2920).
*
* The pins live in `peerDependencies` as exact versions (not `catalog:`) so
* this module reads concrete specs even when the workspace manifest is
@@ -27,6 +36,16 @@ type FastembedModule = typeof Fastembed;
const FASTEMBED_SPEC = packageManifest.peerDependencies.fastembed;
const ORT_SPEC = packageManifest.peerDependencies["onnxruntime-node"];
/** Build the deterministic fastembed runtime install plan used by local embeddings. */
export function fastembedRuntimeInstallPlan(): FastembedRuntimeInstallPlan {
return {
versionKey: `fastembed-${FASTEMBED_SPEC}_ort-${ORT_SPEC}_forced-ort`.replace(/[^A-Za-z0-9._-]/g, "_"),
install: {
dependencies: { fastembed: FASTEMBED_SPEC, "onnxruntime-node": ORT_SPEC },
overrides: { "onnxruntime-common": ORT_SPEC, "onnxruntime-node": ORT_SPEC },
},
};
}
let fastembedLoad: Promise<FastembedModule> | null = null;
export function loadFastembed(): Promise<FastembedModule> {
@@ -42,15 +61,15 @@ async function loadFastembedOnce(): Promise<FastembedModule> {
// 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 ORT 1.24 before fastembed's nested ORT 1.21 — only on Windows,
// 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");
}
return await import("fastembed");
} catch (error) {
if (!isModuleResolutionError(error)) throw error;
logger.debug("mnemopi: fastembed not resolvable, using on-demand runtime install", {
if (!isRecoverableFastembedLoadError(error)) throw error;
logger.debug("mnemopi: fastembed not loadable, using on-demand runtime install", {
error: String(error),
});
return loadFromRuntimeInstall();
@@ -58,10 +77,10 @@ async function loadFastembedOnce(): Promise<FastembedModule> {
}
async function loadFromRuntimeInstall(): Promise<FastembedModule> {
const versionKey = `fastembed-${FASTEMBED_SPEC}_ort-${ORT_SPEC}`.replace(/[^A-Za-z0-9._-]/g, "_");
const plan = fastembedRuntimeInstallPlan();
const runtimeDir = await ensureRuntimeInstalled({
runtimeDir: path.join(getFastembedRuntimeDir(), versionKey),
install: { dependencies: { fastembed: FASTEMBED_SPEC, "onnxruntime-node": ORT_SPEC } },
runtimeDir: path.join(getFastembedRuntimeDir(), plan.versionKey),
install: plan.install,
probePackage: "fastembed",
});
const nodeModules = path.join(runtimeDir, "node_modules");
@@ -80,10 +99,10 @@ async function loadFromRuntimeInstall(): Promise<FastembedModule> {
return requireRuntime(entry) as FastembedModule;
}
function isModuleResolutionError(error: unknown): boolean {
function isRecoverableFastembedLoadError(error: unknown): boolean {
if (typeof error !== "object" || error === null) return false;
const { name, code, message } = error as { name?: unknown; code?: unknown; message?: unknown };
if (name === "ResolveMessage") return true;
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") return true;
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "ERR_DLOPEN_FAILED") return true;
return typeof message === "string" && /cannot find (module|package)/i.test(message);
}
@@ -1,13 +1,15 @@
import { describe, expect, test } from "bun:test";
import rootManifest from "../../../package.json" with { type: "json" };
import packageManifest from "../package.json" with { type: "json" };
import { fastembedRuntimeInstallPlan } from "../src/core/fastembed-runtime";
// The fastembed/onnxruntime-node peers are pinned as exact versions (not
// `catalog:`) because `core/fastembed-runtime.ts` reads them to `bun install`
// the on-demand embedding runtime — including from bundles where the inlined
// manifest would otherwise carry an uninstallable `catalog:` spec (#2389).
// This pins the contract: the runtime install materializes exactly the
// versions the workspace develops and tests against.
// versions the workspace develops and tests against, and forces fastembed's
// transitive ORT request away from its archived 1.21.0 pin (#2920).
describe("fastembed runtime version pins", () => {
const catalog = rootManifest.workspaces.catalog;
@@ -20,4 +22,17 @@ describe("fastembed runtime version pins", () => {
expect(packageManifest.peerDependencies.fastembed).toMatch(/^\d+\.\d+\.\d+$/);
expect(packageManifest.peerDependencies["onnxruntime-node"]).toMatch(/^\d+\.\d+\.\d+$/);
});
test("runtime install overrides fastembed's transitive onnxruntime pin", () => {
const plan = fastembedRuntimeInstallPlan();
expect(plan.install.dependencies).toEqual({
fastembed: packageManifest.peerDependencies.fastembed,
"onnxruntime-node": packageManifest.peerDependencies["onnxruntime-node"],
});
expect(plan.install.overrides).toEqual({
"onnxruntime-common": packageManifest.peerDependencies["onnxruntime-node"],
"onnxruntime-node": packageManifest.peerDependencies["onnxruntime-node"],
});
expect(plan.versionKey).toContain("forced-ort");
});
});