Release rolling / release (push) Successful in 11m19s
Pack writes a qvac-backends/ sidecar next to the booter
148 lines
4.3 KiB
JavaScript
148 lines
4.3 KiB
JavaScript
import test from 'brittle'
|
|
import path from 'path'
|
|
import {
|
|
bareOsQvacSkipFromEnv,
|
|
bareOsQvacFlattenTools,
|
|
bareOsQvacEmitAgentEvent,
|
|
bareOsQvacResolveDeviceConfig,
|
|
bareOsQvacBuildModelConfig,
|
|
bareOsQvacPickVulkanPin,
|
|
createBareOsQvacBridge
|
|
} from './lib/bare-os-qvac-host.mjs'
|
|
|
|
test('BARE_OS_SKIP_QVAC disables bridge', async (t) => {
|
|
t.ok(bareOsQvacSkipFromEnv({ BARE_OS_SKIP_QVAC: '1' }))
|
|
t.ok(bareOsQvacSkipFromEnv({ BARE_OS_SKIP_QVAC: 'true' }))
|
|
t.absent(bareOsQvacSkipFromEnv({}))
|
|
const bridge = createBareOsQvacBridge({ env: { BARE_OS_SKIP_QVAC: '1' } })
|
|
t.absent(bridge.available())
|
|
t.is(bridge.status().status, 'unavailable')
|
|
t.ok(bridge.status().cacheDir)
|
|
t.is(bridge.status().hdmsPath, '/mnt/models')
|
|
await t.exception(() => bridge.loadModel({ modelSrc: 'QWEN3_1_7B_INST_Q4' }))
|
|
})
|
|
|
|
test('resolve device config defaults to auto GPU', async (t) => {
|
|
const d = bareOsQvacResolveDeviceConfig({}, {})
|
|
t.is(d.device, 'gpu')
|
|
t.is(d.mainGpu, 'auto')
|
|
t.absent(d.gpuLayers)
|
|
t.absent(d.noCpuFallback)
|
|
|
|
const cpu = bareOsQvacResolveDeviceConfig({}, { BARE_OS_QVAC_DEVICE: 'cpu' })
|
|
t.is(cpu.device, 'cpu')
|
|
|
|
const idx = bareOsQvacResolveDeviceConfig({ mainGpu: '1' }, {})
|
|
t.is(idx.mainGpu, 1)
|
|
|
|
const cfg = bareOsQvacBuildModelConfig({
|
|
tools: true,
|
|
ctxSize: 4096,
|
|
device: 'gpu',
|
|
mainGpu: 'dedicated'
|
|
})
|
|
t.is(cfg.device, 'gpu')
|
|
t.is(cfg['main-gpu'], 'dedicated')
|
|
t.is(cfg.ctx_size, 4096)
|
|
t.absent(cfg.no_mmap)
|
|
|
|
const cpuCfg = bareOsQvacBuildModelConfig({
|
|
tools: false,
|
|
ctxSize: 2048,
|
|
device: 'cpu',
|
|
mainGpu: 'dedicated'
|
|
})
|
|
t.is(cpuCfg.device, 'cpu')
|
|
t.is(cpuCfg.gpu_layers, 0)
|
|
t.absent(cpuCfg['main-gpu'])
|
|
t.absent(cpuCfg.no_mmap)
|
|
})
|
|
|
|
test('pick vulkan pin prefers highest-VRAM vulkaninfo device', async (t) => {
|
|
// Caller passes rank-sorted list (highest VRAM first).
|
|
const pin = bareOsQvacPickVulkanPin([
|
|
{
|
|
index: 1,
|
|
name: 'NVIDIA',
|
|
memoryBytes: 4 * 1024 * 1024 * 1024,
|
|
deviceType: 'DISCRETE',
|
|
source: 'vulkaninfo'
|
|
},
|
|
{
|
|
index: 0,
|
|
name: 'Intel',
|
|
memoryBytes: 512 * 1024 * 1024,
|
|
deviceType: 'INTEGRATED',
|
|
source: 'vulkaninfo'
|
|
}
|
|
])
|
|
t.is(pin.index, 1)
|
|
|
|
const nvidiaOnly = bareOsQvacPickVulkanPin([
|
|
{
|
|
index: 0,
|
|
name: 'NVIDIA GeForce RTX 3050 Ti',
|
|
memoryBytes: 4 * 1024 * 1024 * 1024,
|
|
deviceType: 'DISCRETE',
|
|
source: 'nvidia-smi'
|
|
}
|
|
])
|
|
t.is(nvidiaOnly.index, 1)
|
|
})
|
|
|
|
test('flatten tools nested → flat', async (t) => {
|
|
const flat = bareOsQvacFlattenTools([
|
|
{
|
|
type: 'function',
|
|
function: {
|
|
name: 'run_command',
|
|
description: 'Run shell',
|
|
parameters: { type: 'object', properties: { cmd: { type: 'string' } } }
|
|
}
|
|
},
|
|
{ type: 'function', name: 'already_flat', parameters: { type: 'object', properties: {} } }
|
|
])
|
|
t.is(flat.length, 2)
|
|
t.is(flat[0].name, 'run_command')
|
|
t.is(flat[1].name, 'already_flat')
|
|
})
|
|
|
|
test('emit agent events from QVAC stream shapes', async (t) => {
|
|
/** @type {Record<string, unknown>[]} */
|
|
const out = []
|
|
const state = { toolIndex: 0 }
|
|
bareOsQvacEmitAgentEvent({ type: 'contentDelta', text: 'hi' }, (e) => out.push(e), state)
|
|
bareOsQvacEmitAgentEvent({ type: 'thinkingDelta', text: 'think' }, (e) => out.push(e), state)
|
|
bareOsQvacEmitAgentEvent(
|
|
{ type: 'toolCall', call: { name: 'read_file', arguments: { path: '/x' }, id: 'c1' } },
|
|
(e) => out.push(e),
|
|
state
|
|
)
|
|
t.is(out[0].type, 'delta_content')
|
|
t.is(out[0].content, 'hi')
|
|
t.is(out[1].type, 'delta_reasoning')
|
|
t.is(out[2].type, 'delta_tool_calls')
|
|
t.is(/** @type {any} */ (out[2]).tool_calls[0].function.name, 'read_file')
|
|
t.ok(String(/** @type {any} */ (out[2]).tool_calls[0].function.arguments).includes('/x'))
|
|
})
|
|
|
|
test('backendsDir resolves to real prebuilds (not app.bundle)', async (t) => {
|
|
const {
|
|
bareOsQvacNativeHostId,
|
|
bareOsQvacResolveBackendsDir
|
|
} = await import('./lib/bare-os-qvac-sdk-bind.mjs')
|
|
const host = bareOsQvacNativeHostId()
|
|
t.ok(/^(darwin|linux|win32)-/.test(host))
|
|
const dir = bareOsQvacResolveBackendsDir()
|
|
// On this CI/dev host the package prebuilds exist on disk.
|
|
if (dir) {
|
|
t.absent(/app\.bundle/i.test(dir))
|
|
t.ok(
|
|
dir.endsWith('prebuilds') ||
|
|
dir.includes(path.join('qvac', 'backends'))
|
|
)
|
|
} else {
|
|
t.pass('no backends for this host (ok)')
|
|
}
|
|
})
|