export function createWebcamTools({ camera, capture, normalizer } = {}) { const guard = () => { if (!camera) throw new Error('webcam is unavailable'); camera.assertActive(); }; return [{ name: 'webcam', permission: 'read', description: 'Capture one frame from the user webcam after Settings → Camera → Allow now. The still is attached for this turn. Do not paste the file path into speech.', parameters: { type: 'object', properties: {} }, execute: async () => { try { guard(); if (!capture?.capture) throw new Error('webcam helper is unavailable'); const raw = `/tmp/jarvis-webcam/frame-${Date.now()}.png`; const grabbed = await capture.capture(raw, { device: camera.device }); if (grabbed?.via) camera.setBackend(grabbed.via); // QVAC MtmdLlm / llama.cpp decode JPEG and PNG, not WebP. const still = `/tmp/jarvis-webcam/still-${Date.now()}.jpg`; const frame = normalizer?.normalize ? await normalizer.normalize(grabbed.path || raw, still) : { path: grabbed.path || raw, mime: 'image/png' }; const mime = frame.mime || 'image/jpeg'; return { ok: true, via: grabbed.via || camera.backend, mime, width: frame.width, height: frame.height, note: 'The webcam still is attached for this turn. Describe what you see. Do not speak the file path.', images: [{ path: frame.path, mime }], }; } catch (error) { return { error: error.message, next_action: 'Press Allow now in Settings, Camera' }; } }, }]; }