import test from 'brittle' import path from 'path' import { bareOsQvacSkipFromEnv, bareOsQvacFlattenTools, bareOsQvacSanitizeHistory, bareOsQvacDetectToolDialect, bareOsQvacEmitAgentEvent, bareOsQvacResolveDeviceConfig, bareOsQvacBuildModelConfig, bareOsQvacPickVulkanPin, createBareOsQvacBridge } from './lib/services/bare-os-qvac-host.mjs' import { bareOsQvacHostIsDarwin } from './lib/services/bare-os-qvac-gpu-probe.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') if (bareOsQvacHostIsDarwin()) { // macOS: ggml-metal exposes one device and only offloads with n_gpu_layers // > 0, so the default pins mainGpu=0 and offloads all layers. t.is(d.mainGpu, 0) t.is(d.gpuLayers, 999) } else { 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) // Explicit gpu_layers is honored on every host (no darwin default override). const explicit = bareOsQvacResolveDeviceConfig({ gpuLayers: 12 }, {}) t.is(explicit.gpuLayers, 12) const explicitCpu = bareOsQvacResolveDeviceConfig( { gpuLayers: 12 }, { BARE_OS_QVAC_DEVICE: 'cpu' } ) t.is(explicitCpu.gpuLayers, 12) 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, 0) const amdOnly = bareOsQvacPickVulkanPin([ { index: 0, name: 'AMD Radeon RX 7900 XTX', memoryBytes: 24 * 1024 * 1024 * 1024, deviceType: 'DISCRETE', source: 'sysfs' } ]) t.is(amdOnly.index, 0) t.ok(/AMD\/RADV/.test(amdOnly.note)) const softOnly = bareOsQvacPickVulkanPin([ { index: 0, name: 'llvmpipe (LLVM 22.1.8, 256 bits)', memoryBytes: 8 * 1024 * 1024 * 1024, deviceType: 'PHYSICAL_DEVICE_TYPE_CPU', source: 'vulkaninfo' } ]) t.is(softOnly.index, null) }) 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('flatten strips nested schema fields QVAC rejects', async (t) => { const flat = bareOsQvacFlattenTools([ { type: 'function', function: { name: 'todo_write', description: 'todos', parameters: { type: 'object', properties: { todos: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' } } } } }, required: ['todos'] } } } ]) t.is(flat[0].parameters.properties.todos.type, 'array') t.absent(flat[0].parameters.properties.todos.items) }) test('sanitize history converts null content + tool_calls to string', async (t) => { const out = bareOsQvacSanitizeHistory([ { role: 'assistant', content: null, tool_calls: [ { function: { name: 'read_file', arguments: '{"path":"/x"}' } } ] }, { role: 'tool', tool_call_id: 'c1', content: 'ok' } ]) t.is(out[0].content.includes('read_file'), true) t.is(out[1].content, 'ok') t.is(bareOsQvacDetectToolDialect('QWEN3_5_2B_MULTIMODAL_Q4_K_M'), 'qwen35') t.is(bareOsQvacDetectToolDialect('QWEN3_1_7B_INST_Q4'), 'hermes') t.is(bareOsQvacDetectToolDialect('LFM2_1_2B_Q4_K_M'), 'pythonic') t.is(bareOsQvacDetectToolDialect('DEEPSEEK_V3_2_CHAT'), 'dsml') const withText = bareOsQvacSanitizeHistory([ { role: 'assistant', content: 'Working.', tool_calls: [ { function: { name: 'read_file', arguments: '{"path":"/x"}' } } ] } ]) t.is(withText[0].content.includes('Working.'), true) t.is(withText[0].content.includes(''), true) }) test('emit agent events from QVAC stream shapes', async (t) => { /** @type {Record[]} */ 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('toolCall emit increments toolIndex once per call', async (t) => { const state = { toolIndex: 0 } /** @type {Record[]} */ const out = [] bareOsQvacEmitAgentEvent( { type: 'toolCall', call: { name: 'get_system_info', arguments: '{}', id: 'a' } }, (e) => out.push(e), state ) bareOsQvacEmitAgentEvent( { type: 'toolCall', call: { name: 'get_system_info', arguments: '{}', id: 'b' } }, (e) => out.push(e), state ) t.is(state.toolIndex, 2) t.is(/** @type {any} */ (out[0]).tool_calls[0].index, 0) t.is(/** @type {any} */ (out[1]).tool_calls[0].index, 1) }) test('backendsDir resolves to real prebuilds (not app.bundle)', async (t) => { const { bareOsQvacNativeHostId, bareOsQvacResolveBackendsDir } = await import('./lib/services/bare-os-qvac-sdk-bind.mjs') const host = bareOsQvacNativeHostId() t.ok(/^(darwin|linux|win32)-/.test(host)) const dir = bareOsQvacResolveBackendsDir() // Dev hosts may use package prebuilds, ~/.bare-os/qvac/backends, or the // packed sidecar at ~/.local/share/bare-os/booter/qvac-backends. if (dir) { t.absent(/app\.bundle/i.test(dir)) t.ok( dir.endsWith('prebuilds') || dir.includes(path.join('qvac', 'backends')) || dir.endsWith('qvac-backends') || dir.includes(path.join('qvac-backends')) ) } else { t.pass('no backends for this host (ok)') } })