/** * Shared QVAC chat catalog + REST /models helpers (preamble for agent and discord-bot). */ /** @type {{ id: string, family: string, label: string, tools: boolean, ramGb: number, profile?: string }[]} */ var BARE_AGENT_QVAC_CHAT_MODELS = [ { id: 'QWEN3_600M_INST_Q4', family: 'qwen3', label: 'Qwen3 0.6B Instruct Q4', tools: true, ramGb: 4, profile: 'lite' }, { id: 'QWEN3_1_7B_INST_Q4', family: 'qwen3', label: 'Qwen3 1.7B Instruct Q4', tools: true, ramGb: 8, profile: 'recommended' }, { id: 'QWEN3_4B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Instruct Q4_K_M', tools: true, ramGb: 16, profile: 'strong' }, { id: 'QWEN3_4B_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Q4_K_M', tools: true, ramGb: 16 }, { id: 'QWEN3_8B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 8B Instruct Q4_K_M', tools: true, ramGb: 24 }, { id: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K', family: 'llama', label: 'Llama 3.2 1B tool-calling', tools: true, ramGb: 6, profile: 'tool-tiny' }, { id: 'LLAMA_3_2_1B_INST_Q4_0', family: 'llama', label: 'Llama 3.2 1B Instruct Q4_0', tools: true, ramGb: 6 }, { id: 'SMOLLM2_360M_INST_Q8', family: 'smol', label: 'SmolLM2 360M Instruct Q8', tools: false, ramGb: 3 }, { id: 'GPT_OSS_20B_INST_Q4_K_M', family: 'gpt-oss', label: 'GPT-OSS 20B Instruct Q4_K_M', tools: true, ramGb: 24 }, { id: 'GEMMA4_2B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 2B multimodal Q4', tools: true, ramGb: 8 }, { id: 'GEMMA4_2B_MULTIMODAL_Q6_K', family: 'gemma', label: 'Gemma 4 2B multimodal Q6', tools: true, ramGb: 10 }, { id: 'GEMMA4_4B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 4B multimodal Q4', tools: true, ramGb: 16 }, { id: 'GEMMA4_31B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 31B multimodal Q4', tools: true, ramGb: 48 }, { id: 'QWEN3VL_2B_MULTIMODAL_Q4_K', family: 'qwen3', label: 'Qwen3-VL 2B multimodal Q4', tools: true, ramGb: 10 }, { id: 'QWEN3_5_2B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 2B multimodal Q4', tools: true, ramGb: 10 }, { id: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 4B multimodal Q4', tools: true, ramGb: 16 }, { id: 'QWEN3_5_0_8B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 8B multimodal Q4', tools: true, ramGb: 24 }, { id: 'QWEN3_5_9B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 9B multimodal Q4', tools: true, ramGb: 28 }, { id: 'QWEN3_6_27B_MULTIMODAL_Q4_K_XL', family: 'large', label: 'Qwen3.6 27B multimodal Q4', tools: true, ramGb: 48 } ] /** @type {Record} */ var BARE_AGENT_REST_MODEL_FALLBACKS = { groq: [ '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', 'meta-llama/llama-4-scout-17b-16e-instruct', 'meta-llama/llama-4-maverick-17b-128e-instruct', 'groq/compound' ], xai: ['grok-4', 'grok-3', 'grok-3-mini', 'grok-3-fast', 'grok-2-1212', 'grok-2-vision-1212'], openai: ['gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4o', 'gpt-4o-mini', 'o4-mini', 'o3'], custom: [] } /** * @param {string} [profileId] */ function bareAgentQvacModelForProfile(profileId) { const id = String(profileId || '').trim().toLowerCase() for (let i = 0; i < BARE_AGENT_QVAC_CHAT_MODELS.length; i++) { if (BARE_AGENT_QVAC_CHAT_MODELS[i].profile === id) return BARE_AGENT_QVAC_CHAT_MODELS[i] } return BARE_AGENT_QVAC_CHAT_MODELS[1] || BARE_AGENT_QVAC_CHAT_MODELS[0] } /** * @param {string} [modelId] */ function bareAgentQvacFindChatModel(modelId) { const id = String(modelId || '').trim() for (let i = 0; i < BARE_AGENT_QVAC_CHAT_MODELS.length; i++) { if (BARE_AGENT_QVAC_CHAT_MODELS[i].id === id) return BARE_AGENT_QVAC_CHAT_MODELS[i] } return null } /** * @param {{ id: string, label?: string, family?: string }[]} models * @param {{ family?: string, query?: string }} [opts] */ function bareAgentFilterModelList(models, opts) { const o = opts && typeof opts === 'object' ? opts : {} const fam = String(o.family || 'all').trim().toLowerCase() const q = String(o.query || '').trim().toLowerCase() const rows = Array.isArray(models) ? models : [] /** @type {{ id: string, label?: string, family?: string }[]} */ const out = [] for (let i = 0; i < rows.length; i++) { const m = rows[i] if (!m || !m.id) continue if (fam && fam !== 'all' && String(m.family || '').toLowerCase() !== fam) continue if (q) { const blob = (String(m.id) + ' ' + String(m.label || '')).toLowerCase() if (blob.indexOf(q) === -1) continue } out.push(m) } return out } /** * @param {unknown} json * @returns {{ id: string, label: string, family: string, owned_by: string }[]} */ function bareAgentParseOpenAiModels(json) { const raw = json && typeof json === 'object' && Array.isArray(/** @type {{ data?: unknown }} */ (json).data) ? /** @type {{ data: unknown[] }} */ (json).data : Array.isArray(json) ? json : [] const skip = /embed|whisper|tts|dall-e|davinci|babbage|audio|moderation|realtime|image|sora/i /** @type {{ id: string, label: string, family: string, owned_by: string }[]} */ const out = [] const seen = Object.create(null) for (let i = 0; i < raw.length; i++) { const row = raw[i] && typeof raw[i] === 'object' ? /** @type {Record} */ (raw[i]) : null if (!row) continue const id = String(row.id || row.name || '').trim() if (!id || seen[id] || skip.test(id)) continue seen[id] = 1 const owned = String(row.owned_by || row.ownedBy || '') out.push({ id: id, label: id, family: owned || 'api', owned_by: owned }) } out.sort(function (a, b) { return a.id.localeCompare(b.id) }) return out } /** * @param {string} [provider] */ function bareAgentRestModelsFallback(provider) { const key = String(provider || 'groq').trim().toLowerCase() const ids = BARE_AGENT_REST_MODEL_FALLBACKS[key] || BARE_AGENT_REST_MODEL_FALLBACKS.groq return (ids || []).map(function (id) { return { id: id, label: id, family: key, owned_by: key } }) } /** * @param {(url: string, init?: object) => Promise<{ ok?: boolean, status?: number, json?: () => Promise, text?: () => Promise }>} fetchFn * @param {{ baseUrl?: string, apiKey?: string }} opts */ async function bareAgentFetchRestModels(fetchFn, opts) { const o = opts && typeof opts === 'object' ? opts : {} const base = String(o.baseUrl || '').trim().replace(/\/+$/, '') if (!base) throw new Error('rest_base_url required') if (typeof fetchFn !== 'function') throw new Error('fetch unavailable') const url = base + '/models' /** @type {Record} */ const headers = { Accept: 'application/json' } const key = String(o.apiKey || '').trim() if (key) headers.Authorization = 'Bearer ' + key const res = await fetchFn(url, { method: 'GET', headers: headers }) if (!res || res.ok === false) { const st = res && res.status != null ? String(res.status) : 'fetch_failed' throw new Error('models_http_' + st) } let json if (typeof res.json === 'function') json = await res.json() else if (typeof res.text === 'function') json = JSON.parse(await res.text()) else throw new Error('models_unreadable') const list = bareAgentParseOpenAiModels(json) if (!list.length) throw new Error('models_empty') return list }