39 lines
1.6 KiB
JavaScript
39 lines
1.6 KiB
JavaScript
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' };
|
|
}
|
|
},
|
|
}];
|
|
}
|