/** * Static @qvac/sdk binding for Bare standalone pack + in-process Bare. * * Imported only via relative dynamic `import('./bare-os-qvac-sdk-bind.mjs')` so * booter startup does not evaluate the SDK (avoids CodeRange OOM). bare-pack * still embeds this module because pack-anchor / host reference the literal path. * * When packing with --skip-qvac, `@qvac/sdk` resolves to build/stubs (see bare-standalone). */ import * as sdkNs from '@qvac/sdk' import { llmPlugin } from '@qvac/sdk/llamacpp-completion/plugin' import LlmLlamacpp from '@qvac/llm-llamacpp' import barePath from 'bare-path' import hostFs from '#host-fs' import { createRequire } from 'module' import { bareOsQvacPrepareHostCacheConfig } from './bare-os-qvac-models-store.mjs' import { bareOsQvacHostBackendsRoot, bundledExecCandidates } from './paths.js' const requireFromBind = createRequire(import.meta.url) /** Well-known ggml backend filenames when bundle readdir fails. */ const KNOWN_GGML_BACKEND_NAMES = [ 'libqvac-ggml-cpu-x64.so', 'libqvac-ggml-cpu-sse42.so', 'libqvac-ggml-cpu-haswell.so', 'libqvac-ggml-cpu-ivybridge.so', 'libqvac-ggml-cpu-sandybridge.so', 'libqvac-ggml-cpu-skylake.so', 'libqvac-ggml-cpu-skylakex.so', 'libqvac-ggml-cpu-alderlake.so', 'libqvac-ggml-cpu-cannonlake.so', 'libqvac-ggml-cpu-cascadelake.so', 'libqvac-ggml-cpu-cooperlake.so', 'libqvac-ggml-cpu-icelake.so', 'libqvac-ggml-cpu-sapphirerapids.so', 'libqvac-ggml-cpu-piledriver.so', 'libqvac-ggml-cpu-zen4.so', 'libqvac-ggml-vulkan.so', 'libqvac-ggml-cpu.dylib', 'libqvac-ggml-metal.dylib', 'qvac-ggml-cpu.dll', 'qvac-ggml-vulkan.dll' ] /** * @param {string} probeDir absolute path to `/qvac__llm-llamacpp` * @returns {boolean} */ function backendsSlotHasCpu(probeDir) { try { if (!hostFs.existsSync(probeDir)) return false return hostFs .readdirSync(probeDir) .some((n) => typeof n === 'string' && /ggml-cpu/i.test(n)) } catch { return false } } /** * @param {string} root prebuilds-equivalent root (contains `/qvac__llm-llamacpp`) * @param {string} slot `/qvac__llm-llamacpp` * @returns {string} root if valid, else '' */ function acceptBackendsRoot(root, slot) { if (!root) return '' const abs = barePath.resolve(root) return backendsSlotHasCpu(barePath.join(abs, slot)) ? abs : '' } /** * Copy ggml backends from srcDir into host cache; return host root or ''. * @param {string} srcDir * @param {string} slot * @param {string[]} [names] * @returns {string} */ function materializeBackendsToHost(srcDir, slot, names) { /** @type {string[]} */ let fileNames = names && names.length ? names.slice() : [] if (!fileNames.length) { try { fileNames = hostFs .readdirSync(srcDir) .filter((n) => typeof n === 'string' && /\.(so|dylib|dll)$/i.test(n)) } catch { fileNames = [] } } if (!fileNames.length) { for (const name of KNOWN_GGML_BACKEND_NAMES) { try { if (hostFs.existsSync(barePath.join(srcDir, name))) fileNames.push(name) } catch { /* ignore */ } } } if (!fileNames.length) return '' const hostRoot = bareOsQvacHostBackendsRoot() const destDir = barePath.join(hostRoot, slot) try { hostFs.mkdirSync(destDir, { recursive: true }) } catch { return '' } for (const name of fileNames) { const from = barePath.join(srcDir, name) const to = barePath.join(destDir, name) try { let srcSize = -1 try { srcSize = hostFs.statSync(from).size } catch { continue } try { if (hostFs.statSync(to).size === srcSize) continue } catch { /* copy */ } if (typeof hostFs.copyFileSync === 'function') { try { hostFs.copyFileSync(from, to) continue } catch { /* fall through */ } } hostFs.writeFileSync(to, hostFs.readFileSync(from)) } catch { /* best-effort per file */ } } return acceptBackendsRoot(hostRoot, slot) } /** * Prefer Bare `require.asset()` so packed `/app.bundle` assets extract to real paths. * @param {string} host * @param {string} slot * @returns {string} */ function materializeBackendsViaAsset(host, slot) { let llmReq try { const main = requireFromBind.resolve('@qvac/llm-llamacpp') llmReq = createRequire(main) } catch { return '' } if (typeof llmReq.asset !== 'function') return '' const hostRoot = bareOsQvacHostBackendsRoot() const destDir = barePath.join(hostRoot, slot) try { hostFs.mkdirSync(destDir, { recursive: true }) } catch { return '' } let wrote = 0 /** @type {string} */ let realAssetDir = '' for (const name of KNOWN_GGML_BACKEND_NAMES) { const rel = `./prebuilds/${host}/qvac__llm-llamacpp/${name}` try { const assetPath = String(llmReq.asset(rel) || '') if (!assetPath) continue const bundleLike = /(?:^|\/)app\.bundle(?:\/|$)/i.test(assetPath) try { const buf = hostFs.readFileSync(assetPath) if (buf && buf.byteLength) { hostFs.writeFileSync(barePath.join(destDir, name), buf) wrote += 1 if (!bundleLike) realAssetDir = barePath.dirname(assetPath) } } catch { if (!bundleLike) { try { if (typeof hostFs.copyFileSync === 'function') { hostFs.copyFileSync(assetPath, barePath.join(destDir, name)) wrote += 1 realAssetDir = barePath.dirname(assetPath) } } catch { /* ignore */ } } } } catch { /* asset missing for this name */ } } void wrote const hostHit = acceptBackendsRoot(hostRoot, slot) if (hostHit) return hostHit // If assets landed in a real prebuilds tree, use that root directly. if (realAssetDir && backendsSlotHasCpu(realAssetDir)) { const prebuildsRoot = barePath.resolve(realAssetDir, '..', '..') const hit = acceptBackendsRoot(prebuildsRoot, slot) if (hit) return hit } return '' } const sdk = sdkNs?.default && typeof sdkNs.default === 'object' ? { ...sdkNs, ...sdkNs.default } : sdkNs /** True when pack remapped @qvac/sdk to the CI stub (e.g. win32-arm64). */ export const qvacBindIsStub = Boolean( sdk && (sdk.BARE_OS_QVAC_SDK_STUB === true || (llmPlugin && typeof llmPlugin === 'object' && /** @type {{ name?: string }} */ (llmPlugin).name === 'qvac-llm-plugin-stub')) ) let llmLoadPatched = false /** * Host id matching `@qvac/llm-llamacpp/prebuilds//`. * @returns {string} */ export function bareOsQvacNativeHostId() { const p = globalThis.process || {} const platform = String(p.platform || 'linux') let arch = String(p.arch || 'x64') if (arch === 'x86_64') arch = 'x64' if (arch === 'aarch64') arch = 'arm64' return platform + '-' + arch } /** * Package root for `@qvac/llm-llamacpp` (may be `/app.bundle/...` in standalone). * @returns {string} */ export function bareOsQvacLlmPackageRoot() { try { return barePath.dirname(requireFromBind.resolve('@qvac/llm-llamacpp')) } catch { try { return barePath.dirname( requireFromBind.resolve('@qvac/llm-llamacpp/package.json') ) } catch { return '' } } } /** * Materialize ggml backend .so files to a real host path. * Bare standalone embeds them under `/app.bundle/...` which dlopen cannot open; * llama.cpp then fails with `make_cpu_buft_list: no CPU backend found`. * * Returns the prebuilds-equivalent root to pass as `backendsDir` * (native appends `/qvac__llm-llamacpp`). * @returns {string} */ export function bareOsQvacEnsureHostBackendsDir() { const host = bareOsQvacNativeHostId() const slot = barePath.join(host, 'qvac__llm-llamacpp') const envOverride = String( globalThis.process?.env?.BARE_OS_QVAC_BACKENDS_DIR || '' ).trim() if (envOverride) { const hit = acceptBackendsRoot(envOverride, slot) if (hit) return hit } // Already materialized under ~/.bare-os/qvac/backends { const hit = acceptBackendsRoot(bareOsQvacHostBackendsRoot(), slot) if (hit) return hit } // Sidecar next to the standalone binary (pack + install.sh). const sidecarRoots = [] for (const dir of bundledExecCandidates()) { sidecarRoots.push(barePath.join(dir, 'qvac-backends')) } try { const home = String( globalThis.process?.env?.HOME || globalThis.process?.env?.USERPROFILE || '' ) if (home) { sidecarRoots.push( barePath.join(home, '.local', 'share', 'bare-os', 'booter', 'qvac-backends') ) sidecarRoots.push( barePath.join(home, '.bare-os', 'qvac', 'backends') ) } } catch { /* ignore */ } for (const beside of sidecarRoots) { const hit = acceptBackendsRoot(beside, slot) if (hit) return hit } // Bare require.asset() → real host path (or readable bytes). { const hit = materializeBackendsViaAsset(host, slot) if (hit) return hit } const pkgRoot = bareOsQvacLlmPackageRoot() if (!pkgRoot) return '' const srcDir = barePath.join(pkgRoot, 'prebuilds', slot) const srcPrebuilds = barePath.join(pkgRoot, 'prebuilds') const bundleLike = /(?:^|\/)app\.bundle(?:\/|$)/i.test(srcDir) // Dev / unpacked: use package prebuilds directly when they exist on disk. if (!bundleLike) { const hit = acceptBackendsRoot(srcPrebuilds, slot) if (hit) return hit } // Copy from package/bundle path into host cache when readable. return materializeBackendsToHost(srcDir, slot) } /** * Absolute backendsDir for llama.cpp (real host path after materialize). * @returns {string} */ export function bareOsQvacResolveBackendsDir() { return bareOsQvacEnsureHostBackendsDir() } /** * llama.cpp `--fit` trial-loads often fail on hybrid Vulkan (and then CPU * inherits the same opaque error). SDK schema rejects `fit`, so inject into * the native config map after transform via the addon constructor path. */ export function bareOsQvacPatchLlmLoadDefaults() { if (llmLoadPatched || qvacBindIsStub) return const proto = LlmLlamacpp && LlmLlamacpp.prototype if (!proto || typeof proto._load !== 'function') return llmLoadPatched = true const orig = proto._load proto._load = async function bareOsQvacPatchedLlmLoad() { const prev = this._config && typeof this._config === 'object' ? this._config : {} /** @type {Record} */ const next = { ...prev } // This fabric build rejects --no-mmap; only inject fit (not in SDK schema). const fitEnv = String( globalThis.process?.env?.BARE_OS_QVAC_FIT ?? 'off' ) .trim() .toLowerCase() if (next.fit == null || next.fit === '') { next.fit = fitEnv === 'on' ? 'on' : 'off' } delete next.no_mmap delete next['no-mmap'] // Re-resolve every load — first patch-time resolve can miss a late sidecar. const backendsDir = bareOsQvacResolveBackendsDir() if (backendsDir) { next.backendsDir = backendsDir } else if (next.backendsDir == null || next.backendsDir === '') { // Avoid silent /app.bundle fallback: leave unset and let native fail loudly, // but surface a clear JS warning once. try { const g = /** @type {any} */ (globalThis) if (!g.__bareOsQvacBackendsDirWarned) { g.__bareOsQvacBackendsDirWarned = true console.warn( '[qvac] ggml backendsDir unresolved — deploy qvac-backends/ beside bare-os-booter, or set BARE_OS_QVAC_BACKENDS_DIR (else: no CPU backend found)' ) } } catch { /* ignore */ } } this._config = next return orig.call(this) } } /** * Register llamacpp completion and return bound SDK surface (registry + load/completion). * Writes Bare-OS host cache config before plugins() so downloads land under * `$BARE_OS_HOST_DATA/qvac/models` instead of `~/.qvac/models`. * @param {{ cacheDir?: string }} [opts] * @returns {Record} */ export function createBoundQvacApi(opts = {}) { if (qvacBindIsStub) { throw new Error( 'QVAC natives not packed for this host (use agent --config REST, or rebuild without --skip-qvac on a supported host)' ) } const prepared = bareOsQvacPrepareHostCacheConfig({ cacheDir: opts.cacheDir }) bareOsQvacPatchLlmLoadDefaults() const pluginsFn = sdk.plugins if (typeof pluginsFn !== 'function') { throw new Error('@qvac/sdk plugins() unavailable') } const bound = pluginsFn([llmPlugin]) return { ...sdk, loadModel: bound.loadModel || sdk.loadModel, completion: bound.completion || sdk.completion, unloadModel: bound.unloadModel || sdk.unloadModel, cancel: bound.cancel || sdk.cancel, __bareOsQvacCacheDir: prepared.cacheDir, __bareOsQvacConfigPath: prepared.configPath, __bareOsQvacBackendsDir: bareOsQvacResolveBackendsDir() } }