/** * QVAC GGUF storage: HDMS /mnt/models (durable) + host cache for llama.cpp load. */ import path from '#host-path' import fs from '#host-fs' import { bareOsQvacHostModelsDir, bareOsQvacHostConfigPath } from '../host/paths.js' export const BARE_OS_QVAC_MODELS_HDMS_LABEL = 'models' export const BARE_OS_QVAC_MODELS_GUEST_ROOT = '/mnt/models' /** * @param {string} [hostCacheDir] * @returns {string} */ export function bareOsQvacModelsHostCacheDir(hostCacheDir) { const d = String(hostCacheDir || '').trim() return d || bareOsQvacHostModelsDir() } /** * Guest path for a GGUF basename under /mnt/models. * @param {string} fileName */ export function bareOsQvacModelsGuestPath(fileName) { const base = String(fileName || '') .replace(/^\/+/, '') .split('/') .pop() return BARE_OS_QVAC_MODELS_GUEST_ROOT + '/' + (base || 'model.gguf') } /** * Host path for a GGUF basename under the cache dir. * @param {string} fileName * @param {string} [hostCacheDir] */ export function bareOsQvacModelsHostPath(fileName, hostCacheDir) { const base = String(fileName || '') .replace(/^\/+/, '') .split('/') .pop() return path.join(bareOsQvacModelsHostCacheDir(hostCacheDir), base || 'model.gguf') } /** * Strip leading content-hash prefix used by QVAC cache filenames. * `5b8aae816570a09d_Qwen3-0.6B-Q4_0.gguf` → `Qwen3-0.6B-Q4_0.gguf` * @param {string} fileName */ export function bareOsQvacModelsStripHashPrefix(fileName) { const base = String(fileName || '') .replace(/^\/+/, '') .split('/') .pop() || '' const m = /^[0-9a-f]{8,}_(.+)$/i.exec(base) return m ? m[1] : base } /** * Whether host file size matches an expected guest size (skip materialize). * @param {string} hostPath * @param {number} guestSize */ export function bareOsQvacHostGgufMatchesSize(hostPath, guestSize) { const want = Number(guestSize) if (!Number.isFinite(want) || want <= 0) return false try { const st = fs.statSync(hostPath) return st.isFile() && st.size === want } catch { return false } } /** * True for a host filesystem path Bare's module resolver cannot load from * `bare:/app.bundle` (absolute POSIX / Windows, not a URL specifier). * @param {string} filePath */ export function bareOsQvacIsHostJsonConfigPath(filePath) { const p = String(filePath || '') if (!/\.json$/i.test(p)) return false // Windows drive paths (`C:\...`) look like a URL scheme if checked first. if (/^[A-Za-z]:[\\/]/.test(p)) return true if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/i.test(p)) return false return p.startsWith('/') } /** * Read a host JSON file for QVAC config. Returns undefined when the path is * not a host .json file or the read/parse fails (caller may delegate). * @param {unknown} id * @returns {unknown | undefined} */ export function bareOsQvacTryReadHostJsonConfig(id) { const filePath = String(id || '') if (!bareOsQvacIsHostJsonConfigPath(filePath)) return undefined try { const raw = fs.readFileSync(filePath, 'utf8') return JSON.parse(raw) } catch { return undefined } } /** * Bare's `@qvac/sdk` resolve-config.bare.js loads config via free `require(path)`. * In standalone that identifier is `globalThis.require` from ctx-bare's * `createRequire(bare:/app.bundle/.../bare-os-ctx-bare.js)`. That resolver * cannot see host files (`MODULE_NOT_FOUND` with `bare:/home/...` candidates). * Always wrap — do not skip when require already exists. */ export function bareOsQvacInstallConfigRequireShim() { const g = globalThis const prev = typeof g.require === 'function' ? g.require : null if (prev && prev.__bareOsQvacConfigRequire) return prev function bareOsQvacConfigRequire(id, ...rest) { const parsed = bareOsQvacTryReadHostJsonConfig(id) if (parsed !== undefined) return parsed if (typeof prev === 'function') { return Reflect.apply(prev, this, [id, ...rest]) } throw new ReferenceError( 'require is not defined (Bare: only host .json config paths are shimmed)' ) } bareOsQvacConfigRequire.__bareOsQvacConfigRequire = true if (prev) { for (const key of ['cache', 'extensions', 'main', 'resolve', 'addon', 'asset']) { try { if (prev[key] !== undefined) bareOsQvacConfigRequire[key] = prev[key] } catch { /* ignore */ } } } g.require = bareOsQvacConfigRequire return bareOsQvacConfigRequire } /** * Write runtime qvac.config.json with absolute cacheDirectory and set QVAC_CONFIG_PATH. * Must run before `@qvac/sdk` plugins() so setSDKConfig sees it once. * @param {{ * cacheDir?: string, * loggerLevel?: string, * loggerConsoleOutput?: boolean * }} [opts] * @returns {{ cacheDir: string, configPath: string }} */ export function bareOsQvacPrepareHostCacheConfig(opts = {}) { bareOsQvacInstallConfigRequireShim() const cacheDir = bareOsQvacModelsHostCacheDir(opts.cacheDir) const configPath = bareOsQvacHostConfigPath() const qvacRoot = path.dirname(configPath) fs.mkdirSync(cacheDir, { recursive: true }) fs.mkdirSync(qvacRoot, { recursive: true }) const procEnv = globalThis.process && globalThis.process.env ? globalThis.process.env : null // Quiet by default — SDK debug + llamacpp chatter floods the agent TTY. // Override with BARE_OS_QVAC_LOG_LEVEL / QVAC_LOG_LEVEL (e.g. debug). const envLevel = procEnv ? String( procEnv.BARE_OS_QVAC_LOG_LEVEL || procEnv.QVAC_LOG_LEVEL || '' ).trim() : '' const loggerLevel = opts.loggerLevel || envLevel || 'error' let loggerConsoleOutput = opts.loggerConsoleOutput if (loggerConsoleOutput === undefined && procEnv) { const ec = String(procEnv.BARE_OS_QVAC_LOGGER_CONSOLE || '').trim() if (ec === '1' || ec === 'true') loggerConsoleOutput = true else if (ec === '0' || ec === 'false') loggerConsoleOutput = false } if (loggerConsoleOutput === undefined) { // Keep console on so real errors still surface; level gates noise. loggerConsoleOutput = true } const body = { plugins: ['@qvac/sdk/llamacpp-completion/plugin'], cacheDirectory: cacheDir, loggerLevel, loggerConsoleOutput: Boolean(loggerConsoleOutput), httpDownloadConcurrency: 3, httpConnectionTimeoutMs: 15000 } fs.writeFileSync(configPath, JSON.stringify(body, null, 2) + '\n') if (procEnv) { procEnv.QVAC_CONFIG_PATH = configPath // Also pin env so @qvac/logging picks the same level before config apply. if (!procEnv.QVAC_LOG_LEVEL) procEnv.QVAC_LOG_LEVEL = loggerLevel } return { cacheDir, configPath } } /** * Ensure writable HDMS label `models` exists (best-effort). * @param {{ * ctx?: Record | null, * hdmsController?: { * active?: boolean, * byLabel?: Map, * create?: (ctx: Record, label: string) => Promise * } | null * }} opts * @returns {Promise<{ ok: boolean, created?: boolean, reason?: string, drive?: unknown }>} */ export async function bareOsQvacEnsureModelsHdms(opts = {}) { const hc = opts.hdmsController const ctx = opts.ctx if (!hc || typeof hc.create !== 'function') { return { ok: false, reason: 'no_hdms_controller' } } if (!hc.active) return { ok: false, reason: 'hdms_inactive' } if (hc.byLabel && hc.byLabel.has(BARE_OS_QVAC_MODELS_HDMS_LABEL)) { const slot = /** @type {{ drive?: unknown }} */ ( hc.byLabel.get(BARE_OS_QVAC_MODELS_HDMS_LABEL) ) return { ok: true, created: false, drive: slot && slot.drive } } if (!ctx || typeof ctx !== 'object') { return { ok: false, reason: 'no_ctx' } } try { await hc.create(ctx, BARE_OS_QVAC_MODELS_HDMS_LABEL) const slot = hc.byLabel ? /** @type {{ drive?: unknown }} */ ( hc.byLabel.get(BARE_OS_QVAC_MODELS_HDMS_LABEL) ) : null return { ok: true, created: true, drive: slot && slot.drive } } catch (e) { const msg = e && typeof e === 'object' && 'message' in e ? String(/** @type {{ message: unknown }} */ (e).message) : String(e) if (/already exists/i.test(msg)) { const slot = hc.byLabel ? /** @type {{ drive?: unknown }} */ ( hc.byLabel.get(BARE_OS_QVAC_MODELS_HDMS_LABEL) ) : null return { ok: true, created: false, drive: slot && slot.drive } } return { ok: false, reason: msg } } } /** * @param {unknown} vfs * @param {string} guestPath * @returns {Promise<{ size: number } | null>} */ export async function bareOsQvacGuestGgufStat(vfs, guestPath) { if (!vfs || typeof vfs !== 'object') return null const v = /** @type {Record} */ (vfs) try { if (typeof v.stat === 'function') { const st = await v.stat(guestPath) const size = Number(st && (st.size ?? st.byteLength)) if (Number.isFinite(size) && size > 0) return { size } } } catch { /* missing */ } return null } /** * Stream Hyperdrive path → host file (avoid buffering full GGUF). * @param {{ createReadStream?: (p: string) => import('stream').Readable, get?: Function }} drive * @param {string} drivePath e.g. /Qwen3-0.6B-Q4_0.gguf * @param {string} hostPath * @returns {Promise} bytes written */ export async function bareOsQvacStreamDriveToHost(drive, drivePath, hostPath) { const rel = String(drivePath || '').startsWith('/') ? String(drivePath) : '/' + String(drivePath || '').replace(/^\/+/, '') if (drive && typeof drive.createReadStream === 'function') { await new Promise((resolve, reject) => { let settled = false const fail = (err) => { if (settled) return settled = true reject(err) } const done = () => { if (settled) return settled = true resolve(undefined) } try { const rs = drive.createReadStream(rel) const ws = fs.createWriteStream(hostPath) rs.on('error', fail) ws.on('error', fail) ws.on('finish', done) if (typeof rs.pipe === 'function') { rs.pipe(ws) } else { rs.on('data', (chunk) => { ws.write(chunk) }) rs.on('end', () => ws.end()) } } catch (e) { fail(e) } }) return fs.statSync(hostPath).size } if (drive && typeof drive.get === 'function') { const buf = await drive.get(rel) if (!buf || !(buf.byteLength > 0 || buf.length > 0)) { throw new Error('empty_drive_get') } const u8 = Buffer.isBuffer(buf) ? buf : Buffer.from( buf.buffer || buf, buf.byteOffset || 0, buf.byteLength || buf.length ) fs.writeFileSync(hostPath, u8) return u8.byteLength } throw new Error('drive_no_stream') } /** * Materialize guest GGUF → host cache path. * Prefers Hyperdrive createReadStream when `drive` is provided; else vfs.readFile. * Skips when host size already matches guest size. * @param {{ * vfs?: Record | null, * guestPath: string, * hostPath: string, * drive?: { createReadStream?: Function, get?: Function } | null, * drivePath?: string, * guestSize?: number * }} o * @returns {Promise<{ ok: boolean, bytes?: number, reason?: string, skipped?: boolean }>} */ export async function bareOsQvacMaterializeGgufToHost(o) { const guestPath = String(o.guestPath || '') const hostPath = String(o.hostPath || '') if (!guestPath || !hostPath) { return { ok: false, reason: 'missing_args' } } try { fs.mkdirSync(path.dirname(hostPath), { recursive: true }) } catch { /* ignore */ } let guestSize = Number(o.guestSize) if (!(Number.isFinite(guestSize) && guestSize > 0) && o.vfs) { const st = await bareOsQvacGuestGgufStat(o.vfs, guestPath) if (st) guestSize = st.size } if ( Number.isFinite(guestSize) && guestSize > 0 && bareOsQvacHostGgufMatchesSize(hostPath, guestSize) ) { return { ok: true, bytes: guestSize, skipped: true, reason: 'size_match' } } const base = guestPath.replace(/^\/+/, '').split('/').pop() || path.basename(hostPath) const drivePath = o.drivePath || (base.startsWith('/') ? base : '/' + base) if (o.drive) { try { const bytes = await bareOsQvacStreamDriveToHost( o.drive, drivePath, hostPath ) return { ok: true, bytes } } catch (e) { const msg = e && typeof e === 'object' && 'message' in e ? String(/** @type {{ message: unknown }} */ (e).message) : String(e) // Fall through to vfs.readFile if (!o.vfs || typeof o.vfs.readFile !== 'function') { return { ok: false, reason: msg } } } } try { if (!o.vfs || typeof o.vfs.readFile !== 'function') { return { ok: false, reason: 'vfs_no_readFile' } } const buf = await o.vfs.readFile(guestPath) if (!buf || !(buf.byteLength > 0 || buf.length > 0)) { return { ok: false, reason: 'empty_guest' } } const u8 = Buffer.isBuffer(buf) ? buf : Buffer.from( buf.buffer || buf, buf.byteOffset || 0, buf.byteLength || buf.length ) fs.writeFileSync(hostPath, u8) return { ok: true, bytes: u8.byteLength } } catch (e) { const msg = e && typeof e === 'object' && 'message' in e ? String(/** @type {{ message: unknown }} */ (e).message) : String(e) return { ok: false, reason: msg } } } /** * Mirror host GGUF into /mnt/models/. * Skips when guest size already matches host. * @param {{ * vfs: Record, * hostPath: string, * guestName?: string * }} o * @returns {Promise<{ ok: boolean, guestPath?: string, reason?: string, skipped?: boolean }>} */ export async function bareOsQvacMirrorHostGgufToHdms(o) { const hostPath = String(o.hostPath || '') if (!hostPath || !o.vfs || typeof o.vfs.writeFile !== 'function') { return { ok: false, reason: 'missing_args' } } const base = String(o.guestName || '') .replace(/^\/+/, '') .split('/') .pop() || path.basename(hostPath) const guestPath = bareOsQvacModelsGuestPath(base) let hostSize = 0 try { hostSize = fs.statSync(hostPath).size } catch { return { ok: false, reason: 'host_missing' } } if (!(hostSize > 0)) return { ok: false, reason: 'empty_host' } const guest = await bareOsQvacGuestGgufStat(o.vfs, guestPath) if (guest && guest.size === hostSize) { return { ok: true, guestPath, skipped: true, reason: 'size_match' } } try { const buf = fs.readFileSync(hostPath) if (!buf || !buf.byteLength) return { ok: false, reason: 'empty_host' } await o.vfs.writeFile(guestPath, buf) return { ok: true, guestPath } } catch (e) { const msg = e && typeof e === 'object' && 'message' in e ? String(/** @type {{ message: unknown }} */ (e).message) : String(e) return { ok: false, reason: msg } } } /** * List GGUF basenames under /mnt/models (best-effort). * @param {Record} vfs * @returns {Promise} */ export async function bareOsQvacListHdmsGgufs(vfs) { if (!vfs || typeof vfs.readdir !== 'function') return [] try { const entries = await vfs.readdir(BARE_OS_QVAC_MODELS_GUEST_ROOT) if (!Array.isArray(entries)) return [] return entries .map((e) => { if (typeof e === 'string') return e if (e && typeof e === 'object' && 'name' in e) return String(e.name) return '' }) .filter((n) => /\.gguf$/i.test(n)) } catch { return [] } } /** * List GGUF basenames in host cache dir. * @param {string} [hostCacheDir] * @returns {string[]} */ export function bareOsQvacListHostGgufs(hostCacheDir) { const dir = bareOsQvacModelsHostCacheDir(hostCacheDir) try { return fs .readdirSync(dir) .filter((n) => typeof n === 'string' && /\.gguf$/i.test(n)) } catch { return [] } } /** * Find a guest GGUF matching model id / cache basename. * @param {Record} vfs * @param {string} hintName e.g. Qwen3-0.6B-Q4_0.gguf or hashed cache name * @returns {Promise} guest absolute path */ export async function bareOsQvacFindHdmsGguf(vfs, hintName) { const names = await bareOsQvacListHdmsGgufs(vfs) if (!names.length) return null const hint = String(hintName || '') const stripped = bareOsQvacModelsStripHashPrefix(hint) for (const n of names) { if (n === hint || n === stripped) return bareOsQvacModelsGuestPath(n) if (stripped && n.endsWith(stripped)) return bareOsQvacModelsGuestPath(n) if (hint && n.includes(stripped)) return bareOsQvacModelsGuestPath(n) } return null } /** * Copy any HDMS GGUFs missing/mismatched on the host cache (before loadModel). * @param {{ * vfs: Record, * hostCacheDir?: string, * drive?: { createReadStream?: Function, get?: Function } | null * }} o * @returns {Promise<{ materialized: string[], skipped: string[], errors: string[] }>} */ export async function bareOsQvacMaterializeHdmsModelsToHost(o) { /** @type {string[]} */ const materialized = [] /** @type {string[]} */ const skipped = [] /** @type {string[]} */ const errors = [] const cacheDir = bareOsQvacModelsHostCacheDir(o.hostCacheDir) try { fs.mkdirSync(cacheDir, { recursive: true }) } catch { /* ignore */ } const names = await bareOsQvacListHdmsGgufs(o.vfs) for (const name of names) { const guestPath = bareOsQvacModelsGuestPath(name) const hostPath = bareOsQvacModelsHostPath(name, cacheDir) const r = await bareOsQvacMaterializeGgufToHost({ vfs: o.vfs, guestPath, hostPath, drive: o.drive || null, drivePath: '/' + name }) if (r.ok && r.skipped) skipped.push(name) else if (r.ok) materialized.push(name) else errors.push(name + ': ' + (r.reason || 'fail')) } return { materialized, skipped, errors } } /** * Mirror host-cache GGUFs into HDMS after download/load (best-effort). * @param {{ * vfs: Record, * hostCacheDir?: string * }} o * @returns {Promise<{ mirrored: string[], skipped: string[], errors: string[] }>} */ export async function bareOsQvacMirrorHostModelsToHdms(o) { /** @type {string[]} */ const mirrored = [] /** @type {string[]} */ const skipped = [] /** @type {string[]} */ const errors = [] const names = bareOsQvacListHostGgufs(o.hostCacheDir) for (const name of names) { const hostPath = bareOsQvacModelsHostPath(name, o.hostCacheDir) const r = await bareOsQvacMirrorHostGgufToHdms({ vfs: o.vfs, hostPath, guestName: name }) if (r.ok && r.skipped) skipped.push(name) else if (r.ok) mirrored.push(name) else errors.push(name + ': ' + (r.reason || 'fail')) } return { mirrored, skipped, errors } }