import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; process.env.XDG_CONFIG_HOME = mkdtempSync(path.join(tmpdir(), 'jarvis-groq-config-')); process.env.XDG_DATA_HOME ||= mkdtempSync(path.join(tmpdir(), 'jarvis-groq-data-')); import test, { afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { EventEmitter } from 'node:events'; import { mkdtemp, writeFile, rm } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { Agent, acquireQvac, releaseQvac, closeQvac, applyAgentInference, qvacStatus } from '../daemon/qvac-master.js'; import { voiceSettings, SETTINGS_FIELDS } from '../daemon/voice-settings.js'; import { JarvisDaemon } from '../daemon/index.js'; import { normalizeSettings, settingVisible, publicSettings } from '../apps/gnome-extension/jarvis@qvac.local/settings-values.js'; const require = createRequire(import.meta.url); const groq = require('../vendor/agent-harness/lib/groq.js'); const webProcess = require('../vendor/agent-harness/lib/web-process.js'); const engine = Agent.engine; function field(key) { return SETTINGS_FIELDS.find((item) => item.key === key); } function resetRemote() { groq.setHttpsRequest(null); engine.configureRemote({ provider: 'local', apiKey: '', model: 'openai/gpt-oss-20b' }); } afterEach(resetRemote); function mockRequest(handler) { return (options, cb) => { const req = new EventEmitter(); req.chunks = []; req.write = (chunk) => { req.chunks.push(Buffer.from(chunk)); }; req.end = () => handler(options, req, cb); req.destroy = () => { req.destroyed = true; }; req.setTimeout = () => {}; return req; }; } test('Groq settings default to local inference and hide cloud fields', () => { const settings = voiceSettings({}); assert.equal(settings.agentInference, 'local'); assert.equal(settings.groqApiKey, ''); assert.equal(settings.groqModel, 'openai/gpt-oss-20b'); assert.equal(settingVisible(field('groqApiKey'), settings), false); assert.equal(settingVisible(field('groqModel'), settings), false); assert.equal(settingVisible(field('modelProfile'), settings), true); const groqSettings = { ...settings, agentInference: 'groq' }; assert.equal(settingVisible(field('groqApiKey'), groqSettings), true); assert.equal(settingVisible(field('modelProfile'), groqSettings), true); assert.equal(settingVisible(field('groqModel'), groqSettings), true); const groqModel = field('groqModel'); assert.equal(groqModel.type, 'choice'); assert.deepEqual(groqModel.options.map((option) => option.value), [ 'openai/gpt-oss-20b', 'openai/gpt-oss-120b', 'llama-3.1-8b-instant', 'llama-3.3-70b-versatile', 'qwen/qwen3.6-27b', 'qwen/qwen3.8-27b', 'minimaxai/minimax-m2.7', 'openai/gpt-oss-safeguard-20b', ]); assert.equal(groqModel.options.some((option) => /compound|whisper|orpheus/i.test(option.value)), false); assert.equal(publicSettings({ groqApiKey: 'gsk_live_secret' }, SETTINGS_FIELDS).groqApiKey, 'set'); assert.equal(normalizeSettings({ groqApiKey: ' gsk_abc ' }, SETTINGS_FIELDS).groqApiKey, 'gsk_abc'); }); test('Groq web processing follows a browser crawl and ignores retired search tools', () => { assert.equal(webProcess.needsGroqWebProcess([{ role: 'user', content: 'weather' }]), false); assert.equal(webProcess.needsGroqWebProcess([ { role: 'user', content: 'weather' }, { role: 'assistant', content: '', tool_calls: [{ name: 'read_file', arguments: {} }] }, ]), false); assert.equal(webProcess.needsGroqWebProcess([ { role: 'user', content: 'weather' }, { role: 'tool', name: 'browser', content: 'url: https://www.startpage.com/\n\npage_text:\nRain in the east.', tool_call_id: 'call_1' }, ]), true); assert.equal(webProcess.needsGroqWebProcess([ { role: 'user', content: 'weather' }, { role: 'tool', name: 'browser', content: 'page_text:\nRain in the east.', tool_call_id: 'call_1' }, { role: 'user', content: 'The attached images are slices of one open page from the top to the bottom. Do not only describe the pictures.', attachments: [{ path: '/tmp/page.jpg' }] }, ]), true); const report = webProcess.pageReport('url: https://www.startpage.com/\n\npage_text:\nFloods close the coast road.\nThe cabinet meets at noon.\n'); assert.match(report, /Floods close the coast road/); assert.match(report, /cabinet meets at noon/); const rewritten = webProcess.asBrowserCall('web_search', { query: 'weather' }); assert.equal(rewritten.name, 'browser'); assert.equal(rewritten.arguments.action, 'navigate'); assert.equal(rewritten.arguments.url, 'https://duckduckgo.com/?q=weather'); assert.deepEqual(webProcess.browserToolsOnly([ { name: 'browser' }, { name: 'read_file' }, ]).map((tool) => tool.name), ['browser']); }); test('Groq maps harness history and tools to OpenAI chat completions', () => { const tools = groq.toOpenAiTools([ { name: 'web_search', description: 'Search', parameters: { type: 'object', properties: { query: { type: 'string' } } } }, ]); assert.equal(tools[0].type, 'function'); assert.equal(tools[0].function.name, 'web_search'); const messages = groq.toOpenAiMessages([ { role: 'system', content: 'You are Jarvis.' }, { role: 'user', content: 'status', attachments: [{ path: '/tmp/still.jpg' }] }, { role: 'assistant', content: '', tool_calls: [{ id: 'call_1', name: 'jarvis_status', arguments: {} }] }, { role: 'tool', name: 'jarvis_status', tool_call_id: 'call_1', content: '{"ok":true}' }, ], { vision: false }); assert.equal(messages[1].content, 'status'); assert.equal(messages[2].tool_calls[0].function.name, 'jarvis_status'); assert.equal(messages[3].role, 'tool'); assert.equal(messages[3].tool_call_id, 'call_1'); assert.equal(messages[3].name, 'jarvis_status'); assert.ok(!JSON.stringify(messages).includes('/tmp/still.jpg')); }); test('Groq vision models attach JPEG stills and text-only models drop them', async () => { const dir = await mkdtemp(path.join(tmpdir(), 'jarvis-groq-vision-')); const still = path.join(dir, 'still.jpg'); await writeFile(still, Buffer.from('ffd8ffe000104a464946', 'hex')); try { const withVision = groq.toOpenAiMessages( [{ role: 'user', content: 'What do you see?', attachments: [{ path: still }] }], { vision: true }, ); assert.equal(withVision[0].content[0].type, 'text'); assert.equal(withVision[0].content[1].type, 'image_url'); assert.match(withVision[0].content[1].image_url.url, /^data:image\/jpeg;base64,/); assert.equal(groq.visionModel('qwen/qwen3.6-27b'), true); assert.equal(groq.visionModel('openai/gpt-oss-20b'), false); } finally { await rm(dir, { recursive: true, force: true }); } }); test('Groq requests pin api.groq.com and redact API keys from errors', async () => { process.env.GROQ_BASE_URL = 'https://evil.example/openai/v1'; let hostname = ''; let auth = ''; groq.setHttpsRequest(mockRequest((options, req, cb) => { hostname = options.hostname; auth = options.headers.Authorization; const res = new EventEmitter(); res.statusCode = 401; cb(res); res.emit('data', Buffer.from(JSON.stringify({ error: { message: 'invalid ' + auth } }))); res.emit('end'); })); try { await assert.rejects( groq.complete({ apiKey: 'gsk_live_secret_value', model: 'openai/gpt-oss-20b', history: [{ role: 'user', content: 'hi' }], }), (error) => { assert.equal(hostname, 'api.groq.com'); assert.equal(groq.groqHost(), 'api.groq.com'); assert.match(error.message, /Groq HTTP 401/); assert.doesNotMatch(error.message, /gsk_live_secret_value/); assert.doesNotMatch(error.message, /Bearer gsk_live_secret_value/); return true; }, ); } finally { delete process.env.GROQ_BASE_URL; } }); test('Groq complete streams content and native tool calls', async () => { groq.setHttpsRequest(mockRequest((options, req, cb) => { assert.equal(options.hostname, 'api.groq.com'); assert.equal(options.path, '/openai/v1/chat/completions'); const body = JSON.parse(Buffer.concat(req.chunks).toString('utf8')); assert.equal(body.model, 'openai/gpt-oss-20b'); assert.equal(body.stream, true); assert.equal(body.parallel_tool_calls, false); assert.equal(body.reasoning_effort, 'low'); assert.equal(body.tools[0].function.name, 'web_search'); const res = new EventEmitter(); res.statusCode = 200; cb(res); const frames = [ 'data: {"choices":[{"delta":{"content":"Looking"}}]}\n', 'data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_9","function":{"name":"web_search","arguments":"{\\"query\\":\\"news\\"}"}}]}}]}\n', 'data: [DONE]\n', ]; for (const frame of frames) res.emit('data', Buffer.from(frame)); res.emit('end'); })); const deltas = []; const result = await groq.complete({ apiKey: 'gsk_test', model: 'openai/gpt-oss-20b', history: [{ role: 'user', content: 'news' }], tools: [{ name: 'web_search', description: 'Search', parameters: { type: 'object', properties: { query: { type: 'string' } } } }], }, (ev) => deltas.push(ev)); assert.equal(result.text, 'Looking'); assert.equal(result.toolCalls[0].name, 'web_search'); assert.equal(result.toolCalls[0].arguments.query, 'news'); assert.equal(deltas[0].type, 'contentDelta'); }); test('Groq refuses compound models so tools stay on this computer', async () => { await assert.rejects( groq.complete({ apiKey: 'gsk_test', model: 'groq/compound', history: [{ role: 'user', content: 'hi' }] }), /tools stay on this computer/, ); assert.equal(groq.reasoningEffort('llama-3.3-70b-versatile'), null); assert.equal(groq.reasoningEffort('qwen/qwen3.6-27b'), 'none'); }); test('Groq thinks only about a browser result and keeps the local chat model', async () => { const original = { resources: Agent.engine.resources, load: Agent.engine.load, close: Agent.engine.close, ensureInit: Agent.engine.ensureInit, }; let loads = 0; Agent.engine.ensureInit = async () => ({}); Agent.engine.resources = async () => ({ gpus: [{}] }); Agent.engine.load = async () => { loads += 1; return { device: 'gpu', modelId: 'local' }; }; Agent.engine.close = async () => {}; try { engine.configureRemote({ provider: 'groq', apiKey: '', model: 'openai/gpt-oss-20b' }); await assert.rejects(engine.complete({ history: [{ role: 'user', content: 'hi' }] }), /no model loaded/); await assert.rejects( engine.complete({ webProcess: true, history: [{ role: 'tool', name: 'browser', content: 'page' }] }), /Groq API key/, ); await acquireQvac(); assert.equal(loads, 1); assert.equal(qvacStatus().agentInference, 'groq'); assert.equal(engine.getLoaded().device, 'groq'); releaseQvac(); engine.configureRemote({ provider: 'groq', apiKey: 'gsk_test', model: 'openai/gpt-oss-20b' }); groq.setHttpsRequest(mockRequest((_options, req, cb) => { const body = JSON.parse(Buffer.concat(req.chunks).toString('utf8')); assert.equal(body.tools.length, 1); assert.equal(body.tools[0].function.name, 'browser'); assert.match(body.messages.map((m) => m.content).join('\n'), /web-page processing only/); const res = new EventEmitter(); res.statusCode = 200; cb(res); res.emit('data', Buffer.from('data: {"choices":[{"delta":{"content":"ok"}}]}\ndata: [DONE]\n')); res.emit('end'); })); const result = await engine.complete({ webProcess: true, history: [{ role: 'tool', name: 'browser', content: '{"text":"page"}', tool_call_id: 'call_1' }], tools: [ { name: 'browser', description: 'Browse', parameters: { type: 'object', properties: {} } }, { name: 'read_file', description: 'Read', parameters: { type: 'object', properties: {} } }, ], }); assert.equal(result.text, 'ok'); assert.equal(loads, 1); } finally { resetRemote(); await closeQvac(); Object.assign(Agent.engine, original); } }); test('applyAgentInference honors env keys and runtimeStatus redacts them', async () => { const previous = process.env.GROQ_API_KEY; process.env.GROQ_API_KEY = 'gsk_env_secret'; try { let unloaded = 0; const previousUnload = Agent.engine.unload; Agent.engine.unload = async () => { unloaded += 1; }; const applied = applyAgentInference({ agentInference: 'groq', groqApiKey: 'gsk_file', groqModel: 'llama-3.1-8b-instant' }); try { assert.equal(applied.provider, 'groq'); assert.equal(applied.model, 'llama-3.1-8b-instant'); assert.equal(applied.keySet, true); await applied.release; assert.equal(unloaded, 0); } finally { Agent.engine.unload = previousUnload; } const daemon = new JarvisDaemon(); daemon.settings = { ...daemon.settings, agentInference: 'groq', groqApiKey: 'gsk_file_secret' }; const status = JSON.parse(daemon.runtimeStatus()); assert.equal(status.agentInference, 'groq'); assert.equal(status.local, false); assert.equal(status.settings.groqApiKey, 'set'); assert.doesNotMatch(daemon.runtimeStatus(), /gsk_file_secret/); assert.doesNotMatch(daemon.runtimeStatus(), /gsk_env_secret/); await daemon.close(); } finally { if (previous === undefined) delete process.env.GROQ_API_KEY; else process.env.GROQ_API_KEY = previous; resetRemote(); } });