Files
bare-operating-system/packages/bare-os-booter/lib/services/bare-os-qvac-host.mjs
T
snxraven 7184b17553
Release rolling / release (push) Successful in 9m38s
Contexts
2026-08-18 21:29:16 -04:00

1360 lines
40 KiB
JavaScript

/**
* In-process QVAC bridge for Bare OS booter.
* Uses static bind module (packed standalone cannot resolve import('@qvac/sdk')
* or relative dynamic import targets that were never static-linked into the graph).
* Set BARE_OS_SKIP_QVAC=1 to disable (CI / pack without natives).
*/
import * as qvacSdkBind from './bare-os-qvac-sdk-bind.mjs'
import {
bareOsQvacProbeGpus,
bareOsQvacMainGpuCandidates,
bareOsQvacHardwareGpus,
bareOsQvacIsSoftwareVulkanDevice,
bareOsQvacApplyHardwareVulkanEnv,
bareOsQvacRunHostCommand,
bareOsQvacHostIsDarwin
} from './bare-os-qvac-gpu-probe.mjs'
import {
bareOsQvacDiagnoseGguf,
bareOsQvacExtractGgufPath
} from './bare-os-qvac-gguf-diagnose.mjs'
import {
BARE_OS_QVAC_MODELS_GUEST_ROOT,
bareOsQvacModelsHostCacheDir,
bareOsQvacMaterializeHdmsModelsToHost,
bareOsQvacMirrorHostModelsToHdms
} from './bare-os-qvac-models-store.mjs'
import { bareOsQvacHostModelsDir } from '../host/paths.js'
/**
* @param {unknown} env
* @returns {boolean}
*/
export function bareOsQvacSkipFromEnv(env) {
const raw =
env && typeof env === 'object'
? /** @type {Record<string, unknown>} */ (env).BARE_OS_SKIP_QVAC
: undefined
const s = String(raw ?? '').trim().toLowerCase()
return s === '1' || s === 'true' || s === 'yes'
}
/**
* Apply Vulkan device visibility before llm-llamacpp enumerates ICDs.
* Hybrid Intel+NVIDIA laptops often list iGPU as Vulkan0; hide it with e.g. `1`.
* @param {Record<string, unknown>} env
* @param {{ forceIndex?: number | null, force?: boolean }} [opts]
* @returns {string} active GGML_VK_VISIBLE_DEVICES value (may be empty)
*/
export function bareOsQvacApplyVulkanEnv(env, opts = {}) {
const procEnv =
globalThis.process && globalThis.process.env
? globalThis.process.env
: null
if (!procEnv) return ''
const fromBare = String(env.BARE_OS_QVAC_VK_VISIBLE_DEVICES ?? '').trim()
if (fromBare) {
procEnv.GGML_VK_VISIBLE_DEVICES = fromBare
return fromBare
}
if (opts.forceIndex != null && Number.isFinite(Number(opts.forceIndex))) {
const v = String(Math.floor(Number(opts.forceIndex)))
if (opts.force || !String(procEnv.GGML_VK_VISIBLE_DEVICES || '').trim()) {
procEnv.GGML_VK_VISIBLE_DEVICES = v
}
return String(procEnv.GGML_VK_VISIBLE_DEVICES || '')
}
return String(procEnv.GGML_VK_VISIBLE_DEVICES || '')
}
/**
* Resolve llm device / main-gpu / gpu_layers from load opts + env.
* Default mainGpu is `auto` (probe highest-VRAM Vulkan device, else try heuristics, else CPU).
* @param {{
* device?: string,
* mainGpu?: string | number,
* gpuLayers?: number,
* }} o
* @param {Record<string, unknown>} env
*/
export function bareOsQvacResolveDeviceConfig(o, env) {
const envDevice = String(env.BARE_OS_QVAC_DEVICE ?? '')
.trim()
.toLowerCase()
let device = String(o.device || envDevice || 'gpu')
.trim()
.toLowerCase()
if (device !== 'cpu' && device !== 'gpu') device = 'gpu'
const envMain = String(env.BARE_OS_QVAC_MAIN_GPU ?? '').trim()
const rawMain =
o.mainGpu !== undefined && o.mainGpu !== null && String(o.mainGpu).trim() !== ''
? o.mainGpu
: envMain || 'auto'
/** @type {string | number} */
let mainGpu = 'auto'
if (typeof rawMain === 'number' && Number.isFinite(rawMain) && rawMain >= 0) {
mainGpu = Math.floor(rawMain)
} else {
const s = String(rawMain).trim().toLowerCase()
if (s === 'auto' || s === 'integrated' || s === 'dedicated') mainGpu = s
else if (/^\d+$/.test(s)) mainGpu = Number.parseInt(s, 10)
}
const envLayers = Number(env.BARE_OS_QVAC_GPU_LAYERS)
const layersRaw =
o.gpuLayers !== undefined && o.gpuLayers !== null
? Number(o.gpuLayers)
: envLayers
/** @type {number | undefined} */
let gpuLayers
if (Number.isFinite(layersRaw) && layersRaw >= 0) {
gpuLayers = Math.floor(layersRaw)
}
const noCpuFallback =
String(env.BARE_OS_QVAC_NO_CPU_FALLBACK ?? '')
.trim()
.toLowerCase() === '1' ||
String(env.BARE_OS_QVAC_NO_CPU_FALLBACK ?? '')
.trim()
.toLowerCase() === 'true'
// macOS: ggml-metal exposes a single device (index 0) and only offloads
// layers when n_gpu_layers > 0 (llama.cpp default is 0 = CPU). Pin
// mainGpu=0 and default gpu_layers to all layers so the Apple GPU is
// actually used instead of silently running CPU.
if (bareOsQvacHostIsDarwin() && device === 'gpu') {
if (typeof mainGpu !== 'number') mainGpu = 0
if (gpuLayers === undefined) gpuLayers = 999
}
return { device, mainGpu, gpuLayers, noCpuFallback }
}
/**
* @param {{
* tools: boolean,
* ctxSize: number,
* device: string,
* mainGpu: string | number,
* gpuLayers?: number,
* verbosity?: number
* }} p
*/
export function bareOsQvacBuildModelConfig(p) {
/** @type {Record<string, unknown>} */
const modelConfig = {
ctx_size: p.ctxSize,
device: p.device
}
// Only send tools:true — false stringifies to "false" and confuses some fabric builds.
if (p.tools) modelConfig.tools = true
if (p.device === 'gpu') {
modelConfig['main-gpu'] = p.mainGpu
}
if (p.gpuLayers !== undefined) {
modelConfig.gpu_layers = p.gpuLayers
} else if (p.device === 'cpu') {
modelConfig.gpu_layers = 0
}
const verb = Number(p.verbosity)
if (Number.isFinite(verb) && verb >= 0 && verb <= 3) {
modelConfig.verbosity = Math.floor(verb)
}
return modelConfig
}
/**
* Pick Vulkan index to pin before first native load (highest VRAM / NVIDIA heuristic).
* @param {Array<{
* index: number,
* name: string,
* memoryBytes: number,
* deviceType: string,
* source: string
* }>} ranked
* @returns {{ index: number | null, note: string }}
*/
export function bareOsQvacPickVulkanPin(ranked) {
const list = bareOsQvacHardwareGpus(Array.isArray(ranked) ? ranked : [])
const vulkanBacked = list.filter((g) =>
String(g.source || '').includes('vulkaninfo')
)
if (vulkanBacked.length) {
const best = vulkanBacked[0]
return {
index: best.index,
note:
'pin GGML_VK_VISIBLE_DEVICES=' +
best.index +
' (' +
best.name +
', ' +
Math.round(best.memoryBytes / (1024 * 1024)) +
'MiB)'
}
}
const nvidia = list.find((g) => /nvidia/i.test(String(g.name || '')))
if (nvidia) {
// After VK_LOADER_DRIVERS_SELECT=*nvidia*, NVIDIA is usually Vulkan0.
// Still try index 0 first via candidates; pin 0 when only nvidia-smi hint.
return {
index: 0,
note:
'pin GGML_VK_VISIBLE_DEVICES=0 (nvidia-smi heuristic for ' +
nvidia.name +
')'
}
}
const amd = list.find((g) =>
/amd|radeon|navi|\brx\s?\d/i.test(String(g.name || ''))
)
if (amd) {
// After RADV-only ICD select, the AMD GPU is Vulkan0.
return {
index: 0,
note:
'pin GGML_VK_VISIBLE_DEVICES=0 (AMD/RADV heuristic for ' +
amd.name +
')'
}
}
return { index: null, note: '' }
}
const BARE_OS_QVAC_PROP_TYPES = Object.freeze([
'string',
'number',
'integer',
'boolean',
'object',
'array'
])
/**
* QVAC `toolSchema` only allows { type, description?, enum? } per property.
* Extra keys (items, default, nested properties) fail the request and the
* SDK then completes with no tools attached.
* @param {unknown} params
* @returns {{ type: 'object', properties: Record<string, unknown>, required?: string[] }}
*/
export function bareOsQvacSanitizeToolParameters(params) {
const src =
params && typeof params === 'object'
? /** @type {Record<string, unknown>} */ (params)
: {}
const rawProps =
src.properties && typeof src.properties === 'object' && !Array.isArray(src.properties)
? /** @type {Record<string, unknown>} */ (src.properties)
: {}
/** @type {Record<string, unknown>} */
const properties = {}
for (const key of Object.keys(rawProps)) {
const v = rawProps[key]
const prop =
v && typeof v === 'object' && !Array.isArray(v)
? /** @type {Record<string, unknown>} */ (v)
: {}
let type = typeof prop.type === 'string' ? prop.type : 'string'
if (BARE_OS_QVAC_PROP_TYPES.indexOf(type) === -1) type = 'string'
/** @type {Record<string, unknown>} */
const next = { type: type }
if (typeof prop.description === 'string' && prop.description) {
next.description = prop.description
}
if (Array.isArray(prop.enum)) {
const en = prop.enum.filter(
(x) =>
typeof x === 'string' ||
typeof x === 'number' ||
typeof x === 'boolean' ||
x === null
)
if (en.length) next.enum = en
}
properties[key] = next
}
/** @type {{ type: 'object', properties: Record<string, unknown>, required?: string[] }} */
const out = { type: 'object', properties }
if (Array.isArray(src.required)) {
const req = src.required
.map((x) => String(x || ''))
.filter((k) => k && Object.prototype.hasOwnProperty.call(properties, k))
if (req.length) out.required = req
}
return out
}
/**
* Flatten OpenAI nested tool defs to QVAC flat shape.
* @param {unknown[]} tools
* @returns {unknown[]}
*/
export function bareOsQvacFlattenTools(tools) {
if (!Array.isArray(tools)) return []
/** @type {unknown[]} */
const out = []
for (const t of tools) {
if (!t || typeof t !== 'object') continue
const o = /** @type {Record<string, unknown>} */ (t)
if (o.function && typeof o.function === 'object') {
const fn = /** @type {Record<string, unknown>} */ (o.function)
const name = typeof fn.name === 'string' ? fn.name : ''
if (!name) continue
out.push({
type: 'function',
name,
description: typeof fn.description === 'string' ? fn.description : '',
parameters: bareOsQvacSanitizeToolParameters(fn.parameters)
})
continue
}
if (typeof o.name === 'string' && o.name) {
out.push({
type: 'function',
name: o.name,
description: typeof o.description === 'string' ? o.description : '',
parameters: bareOsQvacSanitizeToolParameters(o.parameters)
})
}
}
return out
}
/**
* QVAC completion history is `{ role, content: string }` only.
* OpenAI `content: null` + `tool_calls` fails request validation.
* @param {unknown[]} history
* @returns {{ role: string, content: string }[]}
*/
export function bareOsQvacSanitizeHistory(history) {
if (!Array.isArray(history)) return []
/** @type {{ role: string, content: string }[]} */
const out = []
for (const raw of history) {
if (!raw || typeof raw !== 'object') continue
const m = /** @type {Record<string, unknown>} */ (raw)
const role = typeof m.role === 'string' && m.role ? m.role : 'user'
let content = ''
if (typeof m.content === 'string') content = m.content
else if (Array.isArray(m.content)) {
const parts = []
for (const part of m.content) {
if (typeof part === 'string') parts.push(part)
else if (part && typeof part === 'object') {
const o = /** @type {Record<string, unknown>} */ (part)
if (typeof o.text === 'string') parts.push(o.text)
else if (typeof o.content === 'string') parts.push(o.content)
}
}
content = parts.join('')
} else if (m.content != null && typeof m.content !== 'object') {
content = String(m.content)
}
if (Array.isArray(m.tool_calls) && m.tool_calls.length) {
const chunks = []
for (const tc of m.tool_calls) {
if (!tc || typeof tc !== 'object') continue
const o = /** @type {Record<string, unknown>} */ (tc)
const fn =
o.function && typeof o.function === 'object'
? /** @type {Record<string, unknown>} */ (o.function)
: o
const name = typeof fn.name === 'string' ? fn.name : ''
if (!name) continue
let args = fn.arguments
if (args != null && typeof args !== 'string') {
try {
args = JSON.stringify(args)
} catch {
args = '{}'
}
}
if (typeof args !== 'string' || !args) args = '{}'
let argsObj = args
try {
const parsed = JSON.parse(args)
argsObj = JSON.stringify(
parsed && typeof parsed === 'object' ? parsed : {}
)
} catch {
argsObj = '{}'
}
chunks.push(
'<tool_call>{"name":' +
JSON.stringify(name) +
',"arguments":' +
argsObj +
'}</tool_call>'
)
}
if (chunks.length) {
const serialized = chunks.join('\n')
content = content ? content + '\n' + serialized : serialized
}
}
out.push({ role, content })
}
return out
}
/**
* Match QVAC `detectToolDialectFromName` so we do not override auto-detect
* with the wrong parser (e.g. LFM/DeepSeek).
* @param {string} [modelId]
* @returns {'hermes' | 'qwen35' | 'gemma4' | 'harmony' | 'pythonic' | 'dsml'}
*/
export function bareOsQvacDetectToolDialect(modelId) {
const tag = String(modelId || '').toLowerCase()
if (/qwen3[._-]?[56](?![a-z0-9])/.test(tag)) return 'qwen35'
if (/gemma[-_]?4(?=[^a-z0-9]|$)/.test(tag)) return 'gemma4'
if (/gpt[_-]?oss/.test(tag)) return 'harmony'
if (/deepseek[-_. ]?v(?:4|3[._-]?2)(?![0-9])/.test(tag)) return 'dsml'
if (/lfm[_-]?\d/.test(tag)) return 'pythonic'
return 'hermes'
}
/**
* Map one QVAC completion event → agent onEvent payload(s).
* @param {Record<string, unknown>} ev
* @param {(e: Record<string, unknown>) => void} onEvent
* @param {{ toolIndex: number }} state
*/
export function bareOsQvacEmitAgentEvent(ev, onEvent, state) {
if (!ev || typeof ev !== 'object') return
const type = typeof ev.type === 'string' ? ev.type : ''
if (type === 'contentDelta' || type === 'token') {
const text =
typeof ev.text === 'string'
? ev.text
: typeof ev.delta === 'string'
? ev.delta
: typeof ev.content === 'string'
? ev.content
: ''
if (text) onEvent({ type: 'delta_content', content: text })
return
}
if (type === 'thinkingDelta' || type === 'reasoningDelta') {
const text =
typeof ev.text === 'string'
? ev.text
: typeof ev.delta === 'string'
? ev.delta
: ''
if (text) onEvent({ type: 'delta_reasoning', reasoning: text })
return
}
if (type === 'toolCall') {
const call =
(ev.call && typeof ev.call === 'object'
? /** @type {Record<string, unknown>} */ (ev.call)
: null) ||
(ev.toolCall && typeof ev.toolCall === 'object'
? /** @type {Record<string, unknown>} */ (ev.toolCall)
: null) ||
ev
const name =
typeof call.name === 'string'
? call.name
: typeof ev.name === 'string'
? ev.name
: ''
let args = call.arguments ?? ev.arguments ?? {}
if (args != null && typeof args !== 'string') {
try {
args = JSON.stringify(args)
} catch {
args = '{}'
}
}
const idx = state.toolIndex++
const id =
typeof call.id === 'string'
? call.id
: typeof ev.id === 'string'
? ev.id
: 'qvac_call_' + idx
onEvent({
type: 'delta_tool_calls',
tool_calls: [
{
index: idx,
id,
function: {
name,
arguments: typeof args === 'string' ? args : '{}'
}
}
]
})
}
}
/**
* @param {{
* env?: Record<string, unknown>,
* vfs?: Record<string, any> | null,
* ensureModelsDrive?: () => Promise<{
* ok: boolean,
* drive?: unknown,
* reason?: string,
* created?: boolean
* } | null | void>
* }} [opts]
*/
export function createBareOsQvacBridge(opts = {}) {
const env = opts.env && typeof opts.env === 'object' ? opts.env : {}
const vfs =
opts.vfs && typeof opts.vfs === 'object' ? opts.vfs : null
const ensureModelsDrive =
typeof opts.ensureModelsDrive === 'function'
? opts.ensureModelsDrive
: null
const skip = bareOsQvacSkipFromEnv(env)
// Prefer NVIDIA / hardware Vulkan ICDs before any native ggml load.
// macOS uses ggml-metal instead — Vulkan env prep is skipped there.
if (!bareOsQvacHostIsDarwin()) {
bareOsQvacApplyHardwareVulkanEnv(env)
bareOsQvacApplyVulkanEnv(env)
}
/** @type {null | Record<string, any>} */
let api = null
/** @type {Promise<Record<string, any>> | null} */
let apiPromise = null
/** @type {string | null} */
let modelId = null
/** @type {string | null} */
let loadedModelKey = null
/** @type {'idle' | 'downloading' | 'loading' | 'ready' | 'error' | 'unavailable'} */
let status = skip ? 'unavailable' : 'idle'
/** @type {string} */
let lastError = ''
/** @type {string | null} */
let activeRequestId = null
/** @type {string} */
let hostCacheDir = bareOsQvacHostModelsDir()
/** @type {string} */
let backendsDir = ''
function available() {
return !skip && !qvacSdkBind.qvacBindIsStub
}
function getStatus() {
return {
available: available(),
status,
modelId,
loadedModelKey,
lastError: lastError || null,
skip,
activeRequestId,
cacheDir: hostCacheDir,
hdmsPath: BARE_OS_QVAC_MODELS_GUEST_ROOT,
backendsDir: backendsDir || null
}
}
/**
* Ensure HDMS models + materialize guest GGUFs into host cache before load.
* @returns {Promise<{ drive: unknown | null }>}
*/
async function prepareModelsStore() {
/** @type {unknown | null} */
let drive = null
if (ensureModelsDrive) {
try {
const r = await ensureModelsDrive()
if (r && typeof r === 'object' && r.ok && r.drive) drive = r.drive
} catch {
/* best-effort */
}
}
if (vfs) {
try {
await bareOsQvacMaterializeHdmsModelsToHost({
vfs,
hostCacheDir,
drive:
drive && typeof drive === 'object'
? /** @type {{ createReadStream?: Function, get?: Function }} */ (
drive
)
: null
})
} catch {
/* best-effort */
}
}
return { drive }
}
/**
* After download/load, mirror host cache GGUFs into HDMS.
*/
async function mirrorModelsStore() {
if (!vfs) return
try {
await bareOsQvacMirrorHostModelsToHdms({ vfs, hostCacheDir })
} catch {
/* best-effort */
}
}
async function ensureApi() {
if (skip) {
const e = new Error('QVAC disabled (BARE_OS_SKIP_QVAC=1)')
lastError = e.message
status = 'unavailable'
throw e
}
if (api) return api
if (apiPromise) return apiPromise
apiPromise = (async () => {
try {
if (qvacSdkBind.qvacBindIsStub) {
status = 'unavailable'
lastError = 'QVAC natives not packed for this host'
throw new Error(
'QVAC natives not packed for this host; run `agent --config` and choose REST API'
)
}
api = qvacSdkBind.createBoundQvacApi()
if (api && api.__bareOsQvacCacheDir) {
hostCacheDir = bareOsQvacModelsHostCacheDir(
String(api.__bareOsQvacCacheDir)
)
}
try {
backendsDir = String(
api.__bareOsQvacBackendsDir ||
qvacSdkBind.bareOsQvacResolveBackendsDir?.() ||
''
)
} catch {
backendsDir = ''
}
lastError = ''
return api
} catch (err) {
const msg =
err && typeof err === 'object' && 'message' in err
? String(/** @type {{ message: unknown }} */ (err).message)
: String(err)
lastError = msg
if (status !== 'unavailable') status = 'error'
throw new Error('@qvac/sdk unavailable: ' + msg)
}
})()
try {
return await apiPromise
} catch (err) {
apiPromise = null
throw err
}
}
/**
* @param {{
* modelSrc: string,
* tools?: boolean,
* ctxSize?: number,
* device?: string,
* mainGpu?: string | number,
* gpuLayers?: number,
* onProgress?: (p: unknown) => void
* }} o
*/
async function loadModel(o) {
const key = String(o.modelSrc || '').trim()
if (!key) throw new Error('bareOsQvacLoadModel: modelSrc required')
const toolsEnabled = o.tools !== false
// Allow model-card windows (Qwen3 32k, Qwen3.5/Gemma 4 31B 256k, Llama 128k).
// Set BARE_OS_QVAC_MAX_CTX to cap KV on small GPUs.
const maxCtxRaw = Number(env.BARE_OS_QVAC_MAX_CTX)
const hostAbsMax = 262144
const maxCtx =
Number.isFinite(maxCtxRaw) && maxCtxRaw >= 2048
? Math.min(hostAbsMax, Math.floor(maxCtxRaw))
: hostAbsMax
const ctxSize = Math.max(2048, Math.min(maxCtx, Number(o.ctxSize) || 32768))
const resolved = bareOsQvacResolveDeviceConfig(o, env)
const verbEnv = Number(env.BARE_OS_QVAC_VERBOSITY)
// Default quiet (0). Set BARE_OS_QVAC_VERBOSITY=1..3 for llamacpp debug.
const verbosity =
Number.isFinite(verbEnv) && verbEnv >= 0 && verbEnv <= 3
? Math.floor(verbEnv)
: 0
const keyPrefix =
key +
'|ctx=' +
ctxSize +
'|tools=' +
(toolsEnabled ? '1' : '0')
if (modelId && loadedModelKey && loadedModelKey.startsWith(keyPrefix + '|')) {
const wantCpu = resolved.device === 'cpu'
const haveCpu = loadedModelKey.includes('|dev=cpu')
const exactGpu =
loadedModelKey ===
keyPrefix + '|dev=gpu|gpu=' + String(resolved.mainGpu)
const reuse =
(wantCpu && haveCpu) ||
(!wantCpu && resolved.mainGpu === 'auto') ||
(!wantCpu && resolved.mainGpu !== 'auto' && exactGpu)
if (reuse) {
status = 'ready'
return { ok: true, modelId, reused: true }
}
}
if (modelId && api && typeof api.unloadModel === 'function') {
try {
await api.unloadModel({ modelId })
} catch {
/* ignore */
}
modelId = null
loadedModelKey = null
}
/** @type {Array<string | number>} */
let gpuCandidates = []
/** @type {string} */
let probeNote = ''
/** @type {number | null} */
let vkPin = null
const metalHost = bareOsQvacHostIsDarwin()
// Prefer hardware Vulkan ICDs + disable lavapipe BEFORE probe/ensureApi —
// backends init Vulkan once per process. Skipped on macOS (ggml-metal,
// no Vulkan runtime involved).
let hasNvidiaSmi = false
/** @type {ReturnType<typeof bareOsQvacApplyHardwareVulkanEnv>} */
let hwVk = {
note: '',
icds: [],
disabledSoftware: false,
preferNvidia: false,
preferAmd: false,
vendor: ''
}
if (!metalHost) {
try {
const smi = await bareOsQvacRunHostCommand('nvidia-smi', ['-L'], {
timeoutMs: 4000
})
hasNvidiaSmi = Boolean(smi && /GPU\s+\d+:/i.test(smi))
} catch {
hasNvidiaSmi = false
}
hwVk = bareOsQvacApplyHardwareVulkanEnv(env, { hasNvidiaSmi })
if (hwVk.note) {
probeNote = hwVk.note
}
}
const existingVk = String(
(globalThis.process && globalThis.process.env
? globalThis.process.env.GGML_VK_VISIBLE_DEVICES
: '') ||
env.BARE_OS_QVAC_VK_VISIBLE_DEVICES ||
''
).trim()
if (resolved.device === 'cpu') {
gpuCandidates = []
probeNote = 'device=cpu'
} else if (metalHost) {
// ggml-metal: single Metal device (index 0). Probe system_profiler for
// the Apple GPU name/VRAM to report; the candidate is always 0.
let metalNote = 'Metal (Apple GPU)'
try {
const ranked = await bareOsQvacProbeGpus()
const g = bareOsQvacHardwareGpus(ranked)[0] || ranked[0]
if (g) {
const vram =
g.memoryBytes > 0
? ', ' + Math.round(g.memoryBytes / (1024 * 1024)) + 'MiB'
: ''
metalNote = 'Metal (' + g.name + vram + ')'
}
} catch {
/* probe best-effort */
}
gpuCandidates = [0]
probeNote = (probeNote ? probeNote + '; ' : '') + metalNote
} else if (existingVk) {
// Caller already pinned (e.g. GGML_VK_VISIBLE_DEVICES=1) — one attempt only.
gpuCandidates = [0]
probeNote =
(probeNote ? probeNote + '; ' : '') +
'using GGML_VK_VISIBLE_DEVICES=' +
existingVk
} else if (resolved.mainGpu === 'auto') {
try {
const ranked = await bareOsQvacProbeGpus()
const hardware = bareOsQvacHardwareGpus(ranked)
const onlySoftware =
ranked.length > 0 &&
hardware.length === 0 &&
ranked.every((g) => bareOsQvacIsSoftwareVulkanDevice(g))
const pin = bareOsQvacPickVulkanPin(ranked)
vkPin = pin.index
if (pin.note) {
probeNote = (probeNote ? probeNote + '; ' : '') + pin.note
}
if (hardware.length && !pin.note) {
probeNote =
(probeNote ? probeNote + '; ' : '') +
'probed ' +
hardware
.map(
(g) =>
'GPU' +
g.index +
'=' +
g.name +
'(' +
Math.round(g.memoryBytes / (1024 * 1024)) +
'MiB)'
)
.join(', ')
}
if (
onlySoftware &&
!hwVk.preferNvidia &&
!hwVk.preferAmd &&
!hwVk.icds.length
) {
// No hardware ICD and only llvmpipe — skip fake "GPU" load.
gpuCandidates = []
probeNote =
(probeNote ? probeNote + '; ' : '') +
'only software Vulkan (llvmpipe); skipping GPU'
} else if (vkPin == null && (hwVk.preferNvidia || hwVk.preferAmd)) {
// No vulkaninfo, but we selected a single vendor ICD — that
// adapter is Vulkan0 (AMD RADV / NVIDIA after SELECT).
vkPin = 0
probeNote =
(probeNote ? probeNote + '; ' : '') +
'pin GGML_VK_VISIBLE_DEVICES=0 (' +
(hwVk.preferAmd ? 'RADV/AMD' : 'NVIDIA') +
'-only ICD select)'
gpuCandidates = [0]
} else if (vkPin != null) {
// After pin / vendor-only select, selected adapter is Vulkan0.
gpuCandidates = [0]
} else {
gpuCandidates = bareOsQvacMainGpuCandidates(ranked).slice(0, 2)
if (!probeNote) probeNote = 'no vulkan pin; trying heuristics'
}
} catch {
gpuCandidates = ['dedicated', 0]
probeNote =
(probeNote ? probeNote + '; ' : '') +
'probe failed; trying dedicated/0'
}
} else if (
typeof resolved.mainGpu === 'number' &&
Number.isFinite(resolved.mainGpu)
) {
vkPin = Math.floor(resolved.mainGpu)
probeNote =
(probeNote ? probeNote + '; ' : '') +
'pin GGML_VK_VISIBLE_DEVICES=' +
vkPin +
' (explicit)'
gpuCandidates = [0]
} else {
gpuCandidates = [resolved.mainGpu]
}
bareOsQvacApplyVulkanEnv(env, {
forceIndex: vkPin,
force: vkPin != null
})
const s = await ensureApi()
await prepareModelsStore()
status = 'downloading'
const modelSrc = s[key] || key
const onProgress = (prog) => {
const pct =
prog && typeof prog === 'object' && 'percentage' in prog
? Number(/** @type {{ percentage?: unknown }} */ (prog).percentage)
: NaN
if (Number.isFinite(pct) && pct >= 100) status = 'loading'
else status = 'downloading'
if (typeof o.onProgress === 'function') o.onProgress(prog)
}
/**
* @param {ReturnType<typeof bareOsQvacResolveDeviceConfig> & { verbosity?: number }} cfg
*/
async function tryLoad(cfg) {
const modelConfig = bareOsQvacBuildModelConfig({
tools: toolsEnabled,
ctxSize,
device: cfg.device,
mainGpu: cfg.mainGpu,
gpuLayers: cfg.gpuLayers,
verbosity
})
return s.loadModel({
modelSrc,
modelType: 'llm',
modelConfig,
onProgress
})
}
/**
* @param {string | number} mainGpu
* @param {string} [device]
*/
function makeLoadKey(mainGpu, device) {
return (
key +
'|ctx=' +
ctxSize +
'|tools=' +
(toolsEnabled ? '1' : '0') +
'|dev=' +
device +
'|gpu=' +
String(mainGpu)
)
}
/**
* @param {Record<string, unknown>} result
*/
async function finishOk(result) {
await mirrorModelsStore()
return result
}
/** @type {string[]} */
const errors = []
if (resolved.device === 'cpu') {
try {
const id = await tryLoad({
...resolved,
device: 'cpu',
gpuLayers: 0,
mainGpu: 0
})
modelId = id
loadedModelKey = makeLoadKey('cpu', 'cpu')
status = 'ready'
lastError = ''
return await finishOk({ ok: true, modelId, reused: false, device: 'cpu' })
} catch (err) {
const msg =
err && typeof err === 'object' && 'message' in err
? String(/** @type {{ message: unknown }} */ (err).message)
: String(err)
status = 'error'
lastError = msg
throw new Error(msg)
}
}
for (const cand of gpuCandidates) {
try {
const id = await tryLoad({
...resolved,
device: 'gpu',
mainGpu: cand
})
modelId = id
loadedModelKey = makeLoadKey(cand, 'gpu')
status = 'ready'
lastError = probeNote
? 'Using main-gpu=' + String(cand) + ' (' + probeNote + ')'
: ''
return await finishOk({
ok: true,
modelId,
reused: false,
device: 'gpu',
mainGpu: cand,
probe: probeNote || undefined,
vkVisible: bareOsQvacApplyVulkanEnv(env)
})
} catch (err) {
const msg =
err && typeof err === 'object' && 'message' in err
? String(/** @type {{ message: unknown }} */ (err).message)
: String(err)
errors.push('main-gpu=' + String(cand) + ': ' + msg)
if (modelId && typeof s.unloadModel === 'function') {
try {
await s.unloadModel({ modelId })
} catch {
/* ignore */
}
modelId = null
}
}
}
const gpuMsg = errors.length ? errors.join(' | ') : 'no GPU candidates'
if (!resolved.noCpuFallback) {
try {
const id = await tryLoad({
...resolved,
device: 'cpu',
gpuLayers: 0,
mainGpu: 0
})
modelId = id
loadedModelKey = makeLoadKey('cpu', 'cpu')
status = 'ready'
lastError =
'GPU load failed; using CPU. ' +
(probeNote ? probeNote + '. ' : '') +
gpuMsg
return await finishOk({
ok: true,
modelId,
reused: false,
device: 'cpu',
fellBackToCpu: true,
gpuError: gpuMsg,
probe: probeNote || undefined
})
} catch (cpuErr) {
const cpuMsg =
cpuErr && typeof cpuErr === 'object' && 'message' in cpuErr
? String(/** @type {{ message: unknown }} */ (cpuErr).message)
: String(cpuErr)
const ggufPath =
bareOsQvacExtractGgufPath(gpuMsg) || bareOsQvacExtractGgufPath(cpuMsg)
let diag = ''
if (ggufPath) {
try {
diag = await bareOsQvacDiagnoseGguf(ggufPath)
} catch {
diag = ''
}
}
status = 'error'
lastError =
'GPU and CPU load failed. ' +
(probeNote ? probeNote + '. ' : '') +
gpuMsg +
' | cpu: ' +
cpuMsg +
(diag ? ' | ' + diag : '') +
' | tip: check native log for "no CPU backend found" / backendsDir; sha256sum the gguf'
throw new Error(lastError)
}
}
status = 'error'
lastError = gpuMsg
throw new Error(lastError)
}
async function unloadModel() {
if (!modelId || !api) {
modelId = null
loadedModelKey = null
if (!skip && status !== 'error') status = 'idle'
return { ok: true }
}
try {
if (typeof api.unloadModel === 'function') {
await api.unloadModel({ modelId })
}
} catch {
/* ignore */
}
modelId = null
loadedModelKey = null
status = 'idle'
return { ok: true }
}
/**
* @param {{
* history: unknown[],
* tools?: unknown[],
* stream?: boolean,
* captureThinking?: boolean,
* modelSrc?: string,
* toolsEnabled?: boolean,
* ctxSize?: number,
* device?: string,
* mainGpu?: string | number,
* gpuLayers?: number,
* onProgress?: (p: unknown) => void,
* onEvent?: (e: Record<string, unknown>) => void,
* signal?: AbortSignal | null
* }} o
*/
async function complete(o) {
const modelSrc = String(o.modelSrc || loadedModelKey || '').trim()
if (modelSrc) {
if (o.signal && o.signal.aborted) {
const err = new Error('aborted')
err.name = 'AbortError'
throw err
}
const loadP = loadModel({
modelSrc,
tools: o.toolsEnabled !== false,
ctxSize: o.ctxSize,
device: o.device,
mainGpu: o.mainGpu,
gpuLayers: o.gpuLayers,
onProgress: o.onProgress
})
if (o.signal) {
const sig = o.signal
await Promise.race([
loadP,
new Promise((_, reject) => {
const fail = () => {
const err = new Error('aborted')
err.name = 'AbortError'
reject(err)
}
if (sig.aborted) fail()
else sig.addEventListener('abort', fail, { once: true })
})
])
} else {
await loadP
}
if (o.signal && o.signal.aborted) {
const err = new Error('aborted')
err.name = 'AbortError'
throw err
}
}
if (!modelId) throw new Error('bareOsQvacComplete: no model loaded')
const s = await ensureApi()
const onEvent =
typeof o.onEvent === 'function' ? o.onEvent : () => {}
const toolsEnabled = o.toolsEnabled !== false
const flatTools = toolsEnabled
? bareOsQvacFlattenTools(o.tools || [])
: []
const history = bareOsQvacSanitizeHistory(
Array.isArray(o.history) ? o.history : []
)
const toolDialect = bareOsQvacDetectToolDialect(
String(o.modelSrc || loadedModelKey || modelId || '')
)
const run = s.completion({
modelId,
history,
stream: o.stream !== false,
tools: flatTools.length ? flatTools : undefined,
toolDialect,
captureThinking: o.captureThinking !== false
})
activeRequestId = run.requestId || run.id || null
const toolState = { toolIndex: 0 }
let sawTools = false
const signal = o.signal || null
const onAbort = () => {
try {
if (activeRequestId && typeof s.cancel === 'function') {
s.cancel({ requestId: activeRequestId })
}
} catch {
/* ignore */
}
}
if (signal) {
if (signal.aborted) onAbort()
else signal.addEventListener('abort', onAbort, { once: true })
}
/**
* Never hang session teardown on a stuck `run.final` after cancel.
* @param {Promise<unknown> | null | undefined} p
* @param {number} ms
*/
function awaitWithAbortTimeout(p, ms) {
if (!p || typeof p.then !== 'function') return Promise.resolve(null)
if (signal && signal.aborted) return Promise.resolve(null)
return new Promise((resolve) => {
let settled = false
const done = (v) => {
if (settled) return
settled = true
resolve(v)
}
const timer = setTimeout(() => done(null), Math.max(250, ms))
p.then(
(v) => {
clearTimeout(timer)
done(v)
},
() => {
clearTimeout(timer)
done(null)
}
)
if (signal) {
const onA = () => {
clearTimeout(timer)
done(null)
}
if (signal.aborted) onA()
else signal.addEventListener('abort', onA, { once: true })
}
})
}
try {
if (signal && signal.aborted) {
onEvent({ type: 'finish', finish_reason: 'abort' })
return { ok: false, aborted: true }
}
// Honor abort during ensure/load if complete was called mid-exit.
if (modelSrc && signal && signal.aborted) {
onEvent({ type: 'finish', finish_reason: 'abort' })
return { ok: false, aborted: true }
}
if (run.events && typeof run.events[Symbol.asyncIterator] === 'function') {
for await (const ev of run.events) {
if (signal && signal.aborted) break
const rec =
ev && typeof ev === 'object'
? /** @type {Record<string, unknown>} */ (ev)
: {}
if (rec.type === 'toolCall') sawTools = true
bareOsQvacEmitAgentEvent(rec, onEvent, toolState)
}
} else if (
run.tokenStream &&
typeof run.tokenStream[Symbol.asyncIterator] === 'function'
) {
for await (const token of run.tokenStream) {
if (signal && signal.aborted) break
if (typeof token === 'string' && token) {
onEvent({ type: 'delta_content', content: token })
}
}
}
let final = null
if (signal && signal.aborted) {
onAbort()
onEvent({ type: 'finish', finish_reason: 'abort' })
return { ok: false, aborted: true }
}
if (run.final) {
try {
final = await awaitWithAbortTimeout(run.final, 4000)
} catch {
final = null
}
}
if (signal && signal.aborted) {
onEvent({ type: 'finish', finish_reason: 'abort' })
return { ok: false, aborted: true }
}
// Only emit final.toolCalls when none were streamed — QVAC often
// delivers the same calls on the event stream and again on final.
if (
!sawTools &&
toolState.toolIndex === 0 &&
final &&
Array.isArray(final.toolCalls) &&
final.toolCalls.length
) {
sawTools = true
for (const tc of final.toolCalls) {
if (!tc || typeof tc !== 'object') continue
bareOsQvacEmitAgentEvent(
{ type: 'toolCall', call: tc },
onEvent,
toolState
)
}
} else if (final && Array.isArray(final.toolCalls) && final.toolCalls.length) {
sawTools = true
}
if (final && typeof final.contentText === 'string' && final.contentText && toolState.toolIndex === 0) {
/* content already streamed via deltas in normal path */
}
onEvent({
type: 'finish',
finish_reason: sawTools || toolState.toolIndex > 0 ? 'tool_calls' : 'stop'
})
if (final && final.stats) {
onEvent({ type: 'usage', usage: final.stats })
}
return { ok: true, final }
} finally {
if (signal) {
try {
signal.removeEventListener('abort', onAbort)
} catch {
/* ignore */
}
}
activeRequestId = null
}
}
function cancelActive() {
try {
if (activeRequestId && api && typeof api.cancel === 'function') {
api.cancel({ requestId: activeRequestId })
}
} catch {
/* ignore */
}
}
/**
* Best-effort teardown for session exit (never hangs the booter).
* @param {{ timeoutMs?: number }} [opts]
*/
async function dispose(opts = {}) {
cancelActive()
const timeoutMs = Math.max(200, Number(opts.timeoutMs) || 2500)
try {
await Promise.race([
unloadModel(),
new Promise((resolve) => setTimeout(resolve, timeoutMs))
])
} catch {
/* ignore */
}
return { ok: true }
}
return {
available,
status: getStatus,
loadModel,
unloadModel,
complete,
cancelActive,
dispose,
flattenTools: bareOsQvacFlattenTools
}
}