Files
bare-operating-system/packages/bare-os-booter/lib/bare-os-qvac-models-store.mjs
T
Raven Scott 2691f59872
Release rolling / release (push) Successful in 8m48s
Updates
2026-07-31 20:01:56 -04:00

554 lines
17 KiB
JavaScript

/**
* 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 './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
}
}
/**
* Bare's `@qvac/sdk` resolve-config.bare.js loads config via `require(path)`,
* which is undefined in Bare ESM/standalone. Provide a JSON-only shim so
* `QVAC_CONFIG_PATH` pointing at qvac.config.json works.
*/
export function bareOsQvacInstallConfigRequireShim() {
const g = globalThis
if (typeof g.require === 'function') return
g.require = function bareOsQvacConfigRequire(id) {
const filePath = String(id || '')
if (!/\.json$/i.test(filePath)) {
throw new ReferenceError(
'require is not defined (Bare: only .json config paths are shimmed)'
)
}
const raw = fs.readFileSync(filePath, 'utf8')
return JSON.parse(raw)
}
}
/**
* 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<string, unknown> | null,
* hdmsController?: {
* active?: boolean,
* byLabel?: Map<string, unknown>,
* create?: (ctx: Record<string, unknown>, label: string) => Promise<void>
* } | 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<string, any>} */ (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<number>} 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<string, any> | 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/<basename>.
* Skips when guest size already matches host.
* @param {{
* vfs: Record<string, any>,
* 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<string, any>} vfs
* @returns {Promise<string[]>}
*/
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<string, any>} vfs
* @param {string} hintName e.g. Qwen3-0.6B-Q4_0.gguf or hashed cache name
* @returns {Promise<string | null>} 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<string, any>,
* 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<string, any>,
* 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 }
}