/** * QVAC helpers for /bin/agent (no SDK import — host bridge via ctx.bareOsQvac*). */ /** @typedef {'lite'|'recommended'|'strong'|'tool-tiny'} BareAgentQvacProfileId */ /** * Native context windows from upstream model cards (not conservative laptop defaults). * Qwen3 dense (0.6B/1.7B/4B): https://huggingface.co/Qwen/Qwen3-0.6B — 32,768 * Llama 3.2 1B (tool-calling finetune base): Meta Llama 3.2 — 128,000 * Absolute ceiling for overrides / host clamp. */ const BARE_AGENT_QVAC_CTX_QWEN3 = 32768 const BARE_AGENT_QVAC_CTX_LLAMA32_1B = 131072 const BARE_AGENT_QVAC_CTX_ABSOLUTE_MAX = 131072 /** * @typedef {{ * id: BareAgentQvacProfileId, * label: string, * description: string, * chatModel: string, * tools: boolean, * minRamGb: number, * minDiskGb: number, * approxDownloadGb: number, * ctxSize: number * }} BareAgentQvacProfile */ /** @type {Record} */ const BARE_AGENT_QVAC_PROFILES = { lite: { id: 'lite', label: 'Lite', description: 'Smallest download (Qwen3-0.6B). Model-card context 32k; tools enabled.', chatModel: 'QWEN3_600M_INST_Q4', tools: true, minRamGb: 4, minDiskGb: 2, approxDownloadGb: 0.5, ctxSize: BARE_AGENT_QVAC_CTX_QWEN3 }, recommended: { id: 'recommended', label: 'Recommended', description: 'Best balance for Bare OS agent tool calling (Qwen3-1.7B, model-card context 32k).', chatModel: 'QWEN3_1_7B_INST_Q4', tools: true, minRamGb: 8, minDiskGb: 5, approxDownloadGb: 2.5, ctxSize: BARE_AGENT_QVAC_CTX_QWEN3 }, strong: { id: 'strong', label: 'Strong', description: 'Better reasoning (Qwen3-4B). Model-card context 32k (131k with YaRN not enabled).', chatModel: 'QWEN3_4B_INST_Q4_K_M', tools: true, minRamGb: 16, minDiskGb: 8, approxDownloadGb: 3.5, ctxSize: BARE_AGENT_QVAC_CTX_QWEN3 }, 'tool-tiny': { id: 'tool-tiny', label: 'Tool-tiny', description: 'Llama 3.2 1B tool-calling fallback (model-card context 128k).', chatModel: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K', tools: true, minRamGb: 6, minDiskGb: 3, approxDownloadGb: 1, ctxSize: BARE_AGENT_QVAC_CTX_LLAMA32_1B } } /** * Model-card / train context for a QVAC registry id (fallback 32k). * @param {string} [modelId] * @returns {number} */ function bareAgentQvacModelCardCtxSize(modelId) { const id = String(modelId || '') .trim() .toUpperCase() if (!id) return BARE_AGENT_QVAC_CTX_QWEN3 if (id.includes('LLAMA') || id.includes('LLAMA_TOOL')) { return BARE_AGENT_QVAC_CTX_LLAMA32_1B } if (id.includes('QWEN3')) return BARE_AGENT_QVAC_CTX_QWEN3 return BARE_AGENT_QVAC_CTX_QWEN3 } /** @returns {BareAgentQvacProfile[]} */ function bareAgentQvacProfileList() { return Object.values(BARE_AGENT_QVAC_PROFILES) } /** * @param {string} [id] * @returns {BareAgentQvacProfile} */ function bareAgentQvacGetProfile(id) { const key = String(id || '').trim().toLowerCase() return BARE_AGENT_QVAC_PROFILES[key] || BARE_AGENT_QVAC_PROFILES.recommended } /** * Flatten OpenAI nested tool defs → QVAC flat shape. * @param {unknown[]} tools * @returns {unknown[]} */ function bareAgentFlattenToolsForQvac(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} */ (t) if (o.function && typeof o.function === 'object') { const fn = /** @type {Record} */ (o.function) out.push({ type: 'function', name: typeof fn.name === 'string' ? fn.name : '', description: typeof fn.description === 'string' ? fn.description : '', parameters: fn.parameters && typeof fn.parameters === 'object' ? fn.parameters : { type: 'object', properties: {} } }) continue } if (typeof o.name === 'string') { out.push({ type: 'function', name: o.name, description: typeof o.description === 'string' ? o.description : '', parameters: o.parameters && typeof o.parameters === 'object' ? o.parameters : { type: 'object', properties: {} } }) } } return out.filter((x) => x && typeof x === 'object' && /** @type {any} */ (x).name) } /** * @param {Record} ctx * @returns {boolean} */ function bareAgentQvacBridgeAvailable(ctx) { if (!ctx || typeof ctx !== 'object') return false if (typeof ctx.bareOsQvacAvailable === 'function') { try { return Boolean(ctx.bareOsQvacAvailable()) } catch { return false } } return typeof ctx.bareOsQvacComplete === 'function' } /** * Normalize backend from config (qvac | rest). * @param {Record} config * @returns {'qvac'|'rest'} */ function bareAgentResolveBackend(config) { const b = String(config?.backend || '').trim().toLowerCase() if (b === 'rest' || b === 'openai' || b === 'http') return 'rest' if (b === 'qvac') return 'qvac' const p = String(config?.provider || '').trim().toLowerCase() if (p === 'qvac') return 'qvac' if (p === 'groq' || p === 'xai' || p === 'openai' || p === 'custom') return 'rest' // Default: qvac when key unset; rest when key present (legacy configs) if (config?.rest_api_key && String(config.rest_api_key).trim()) return 'rest' return 'qvac' } /** @type {readonly string[]} */ var BARE_AGENT_QVAC_ONLY_KEYS = Object.freeze([ 'qvac_model', 'qvac_profile', 'qvac_ctx_size', 'qvac_device', 'qvac_main_gpu', 'qvac_gpu_layers' ]) /** @type {readonly string[]} */ var BARE_AGENT_REST_ONLY_KEYS = Object.freeze(['rest_base_url', 'rest_api_key']) /** * @typedef {{ * id: string, * label: string, * rest_base_url: string, * default_model: string, * models: string[] * }} BareAgentRestProvider */ /** @type {Record} */ const BARE_AGENT_REST_PROVIDERS = { groq: { id: 'groq', label: 'Groq', rest_base_url: 'https://api.groq.com/openai/v1', default_model: 'llama-3.3-70b-versatile', models: [ 'llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'openai/gpt-oss-120b', 'openai/gpt-oss-20b', 'qwen/qwen3-32b', 'moonshotai/kimi-k2-instruct' ] }, xai: { id: 'xai', label: 'xAI (Grok)', rest_base_url: 'https://api.x.ai/v1', default_model: 'grok-4', models: ['grok-4', 'grok-3', 'grok-3-mini', 'grok-3-fast', 'grok-2-1212'] }, openai: { id: 'openai', label: 'OpenAI', rest_base_url: 'https://api.openai.com/v1', default_model: 'gpt-4.1', models: ['gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4o', 'o4-mini'] }, custom: { id: 'custom', label: 'Custom OpenAI-compatible', rest_base_url: '', default_model: '', models: [] } } /** @returns {BareAgentRestProvider[]} */ function bareAgentRestProviderList() { return [BARE_AGENT_REST_PROVIDERS.groq, BARE_AGENT_REST_PROVIDERS.xai, BARE_AGENT_REST_PROVIDERS.openai, BARE_AGENT_REST_PROVIDERS.custom] } /** * @param {string} [id] * @returns {BareAgentRestProvider} */ function bareAgentRestGetProvider(id) { const key = String(id || '').trim().toLowerCase() if (key === 'http' || key === 'rest') return BARE_AGENT_REST_PROVIDERS.custom return BARE_AGENT_REST_PROVIDERS[key] || BARE_AGENT_REST_PROVIDERS.groq } /** * @param {string} [model] */ function bareAgentIsQvacModelId(model) { const m = String(model || '').trim() if (!m) return false return ( /^QWEN/i.test(m) || /^LLAMA_TOOL/i.test(m) || /QWEN3/i.test(m) || /LLAMA_TOOL_CALLING/i.test(m) ) } /** * Drop keys that belong to the other backend so config.json matches the choice. * @param {Record} config * @returns {Record} */ function bareAgentSanitizeConfigForBackend(config) { const out = { ...(config && typeof config === 'object' ? config : {}) } const backend = bareAgentResolveBackend(out) out.backend = backend if (backend === 'rest') { for (let i = 0; i < BARE_AGENT_QVAC_ONLY_KEYS.length; i++) { delete out[BARE_AGENT_QVAC_ONLY_KEYS[i]] } const prov = String(out.provider || '') .trim() .toLowerCase() if (!prov || prov === 'qvac') out.provider = 'groq' if (bareAgentIsQvacModelId(String(out.model || ''))) { const spec = bareAgentRestGetProvider(String(out.provider || 'groq')) if (spec.default_model) out.model = spec.default_model } } else { for (let i = 0; i < BARE_AGENT_REST_ONLY_KEYS.length; i++) { delete out[BARE_AGENT_REST_ONLY_KEYS[i]] } out.provider = 'qvac' } return out } /** * @param {string} [secret] */ function bareAgentMaskSecretPreview(secret) { const t = String(secret || '') if (!t.trim()) return '(not set)' if (t.length <= 8) return '********' return t.slice(0, 3) + '…' + t.slice(-4) } /** * Resolve context window for QVAC load from the model card / profile. * Legacy undersized `qvac_ctx_size` values (e.g. 4096/8192) are ignored so * setup stays automatic; only overrides ≥ the profile default apply. * Host may still cap via `BARE_OS_QVAC_MAX_CTX`. * @param {Record} config * @param {BareAgentQvacProfile} profile */ function bareAgentQvacResolveCtxSize(config, profile) { const modelId = String( (config && (config.qvac_model || config.model)) || (profile && profile.chatModel) || '' ) const cardCtx = bareAgentQvacModelCardCtxSize(modelId) const profileCtx = Math.max( 2048, Number(profile && profile.ctxSize) || cardCtx ) let ctx = Math.min( BARE_AGENT_QVAC_CTX_ABSOLUTE_MAX, Math.max(profileCtx, cardCtx) ) const raw = Number(config && config.qvac_ctx_size) if (Number.isFinite(raw) && raw >= profileCtx) { ctx = Math.min(BARE_AGENT_QVAC_CTX_ABSOLUTE_MAX, Math.floor(raw)) } // Full Bare OS tool list ≈ 4.5k tokens; leave room for system + reply. // Tools are always on for agent sessions — keep enough ctx for schemas. if (ctx < 8192) ctx = 8192 return ctx } /** * Device / main-gpu / layers for QVAC load (config + profile defaults). * @param {Record} config * @returns {{ device?: string, mainGpu?: string | number, gpuLayers?: number }} */ function bareAgentQvacResolveDeviceOpts(config) { /** @type {{ device?: string, mainGpu?: string | number, gpuLayers?: number }} */ const out = {} const device = String(config && config.qvac_device ? config.qvac_device : '') .trim() .toLowerCase() if (device === 'cpu' || device === 'gpu') out.device = device const mainRaw = config && config.qvac_main_gpu if (mainRaw !== undefined && mainRaw !== null && String(mainRaw).trim() !== '') { const s = String(mainRaw).trim().toLowerCase() if (s === 'auto' || s === 'dedicated' || s === 'integrated') out.mainGpu = s else if (/^\d+$/.test(s)) out.mainGpu = Number.parseInt(s, 10) } else { out.mainGpu = 'auto' } const layers = Number(config && config.qvac_gpu_layers) if (Number.isFinite(layers) && layers >= 0) out.gpuLayers = Math.floor(layers) return out }