Skip to content

Commit 0c68b26

Browse files
authored
fix(nix): package local speech worker (#1587)
* fix(nix): package local speech worker * fix(nix): reuse sherpa package helper * fix(nix): patch sherpa-onnx prebuilt binaries for NixOS libstdc++ The prebuilt sherpa-onnx-linux-x64 .so files link against libstdc++.so.6 which is not on the NixOS library path. Add autoPatchelfHook to fix ELF RPATHs and stdenv.cc.cc.lib to provide the C++ runtime, resolving the "Failed to load model because protobuf parsing failed" SIGABRT at speech worker startup.
1 parent 3d86c73 commit 0c68b26

2 files changed

Lines changed: 31 additions & 14 deletions

File tree

nix/package.nix

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
nodejs_22,
66
python3,
77
makeWrapper,
8+
autoPatchelfHook,
89
# node-pty needs libuv headers on Linux
910
libuv,
1011
# Exposed so downstream flakes that follow a different nixpkgs revision
@@ -59,10 +60,13 @@ buildNpmPackage rec {
5960
nativeBuildInputs = [
6061
python3 # for node-gyp (node-pty compilation)
6162
makeWrapper
63+
] ++ lib.optionals stdenv.hostPlatform.isLinux [
64+
autoPatchelfHook
6265
];
6366

6467
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [
6568
libuv
69+
stdenv.cc.cc.lib # libstdc++ for sherpa-onnx prebuilt binaries
6670
];
6771

6872
# Don't use the default npm build hook — we need a custom build sequence
@@ -71,10 +75,9 @@ buildNpmPackage rec {
7175
buildPhase = ''
7276
runHook preBuild
7377
74-
# Rebuild only node-pty (native addon for terminal emulation).
75-
# Speech-related native modules (sherpa-onnx, onnxruntime-node) are
76-
# intentionally left unbuilt — they're lazily loaded and gracefully
77-
# degrade when unavailable.
78+
# Rebuild only node-pty (native addon for terminal emulation). The sherpa
79+
# speech runtime ships prebuilt platform packages and is copied into the
80+
# daemon closure by scripts/trace-daemon.mjs.
7881
npm rebuild node-pty
7982
8083
# Build all server packages in dependency order (defined in package.json)
@@ -89,7 +92,7 @@ buildNpmPackage rec {
8992
9093
# Compute the daemon's runtime closure by static module-graph tracing
9194
# (@vercel/nft from supervisor-entrypoint.js, cli/dist/index.js, and the
92-
# forked terminal-worker-process.js) plus an explicit list of non-JS
95+
# forked terminal/speech worker processes) plus an explicit list of non-JS
9396
# assets read at runtime. The trace script is the single source of
9497
# truth for what the daemon needs at $out — auditable in plain JS, no
9598
# npm hoisting / .bin / workspace-symlink footguns.

scripts/trace-daemon.mjs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
// Emit the set of files the daemon and CLI need at runtime, computed by
3-
// static module-graph tracing (@vercel/nft) from the three entry points.
3+
// static module-graph tracing (@vercel/nft) from the daemon entry points.
44
// Used by nix/package.nix's installPhase to materialize $out/lib/paseo
55
// with only the bytes the daemon actually loads — no Expo, RN, Metro,
66
// Electron, ML stacks, or other non-daemon workspace bloat.
@@ -15,16 +15,27 @@
1515
import { nodeFileTrace } from "@vercel/nft";
1616
import { glob } from "node:fs/promises";
1717
import path from "node:path";
18+
import { pathToFileURL } from "node:url";
1819

1920
const REPO_ROOT = path.resolve(import.meta.dirname, "..");
2021

21-
// Three entry points. The terminal worker is forked into its own Node
22-
// process and has its own require tree (node-pty, etc.) — nft does not
23-
// follow fork boundaries, so it must be traced separately.
22+
const { sherpaPlatformPackageName } = await import(
23+
pathToFileURL(
24+
path.join(
25+
REPO_ROOT,
26+
"packages/server/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js",
27+
),
28+
).href
29+
);
30+
31+
// Daemon entry points. Workers forked into their own Node processes have
32+
// independent require trees; nft does not follow fork boundaries, so trace
33+
// them separately.
2434
const entries = [
2535
"packages/cli/dist/index.js",
2636
"packages/server/dist/scripts/supervisor-entrypoint.js",
2737
"packages/server/dist/server/terminal/terminal-worker-process.js",
38+
"packages/server/dist/server/server/speech/providers/local/worker-process.js",
2839
];
2940

3041
// Files read at runtime via fs APIs rather than `require`. nft only
@@ -44,19 +55,22 @@ const additionalInputs = [
4455
// the Nix derivation builds for one platform at a time and ships only
4556
// its own binaries.
4657
`node_modules/node-pty/prebuilds/${process.platform}-${process.arch}/**`,
58+
// sherpa-onnx-node dynamically resolves a platform-specific native package.
59+
// Copy the wrapper plus the host platform package explicitly.
60+
"node_modules/sherpa-onnx-node/**",
61+
`node_modules/${sherpaPlatformPackageName()}/**`,
4762
];
4863

4964
// Trace.
5065
const { fileList, warnings } = await nodeFileTrace(entries, {
5166
base: REPO_ROOT,
5267
// Tolerate the conditional / dynamic patterns we already audited:
53-
// sherpa-onnx-${platform}-${arch} package resolution (sherpa is
54-
// intentionally not built in the Nix sandbox; voice features degrade
55-
// gracefully when unavailable), and a handful of test-only requires
56-
// that get tree-shaken out by tsc.
68+
// sherpa-onnx-${platform}-${arch} package resolution (the host package
69+
// is copied explicitly above), and a handful of test-only requires that
70+
// get tree-shaken out by tsc.
5771
ignore: [
5872
// Cross-platform native packages for the sherpa speech runtime;
59-
// unsupported in the Nix build, lazily loaded when present.
73+
// only the host platform package is needed at runtime.
6074
"sherpa-onnx-*/**",
6175
// Platform-specific clipboard variants; only the host's variant
6276
// is needed at runtime, and the package's index.js resolver picks

0 commit comments

Comments
 (0)