UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

253 lines (247 loc) • 9.71 kB
'use strict'; var fs = require('node:fs'); var path = require('node:path'); var persistence = require('./types-BYXHizYk.cjs'); function sniffMimeFromBytes(buf) { if (buf.length < 4) return void 0; const b = buf instanceof Buffer ? buf : Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) return "image/png"; if (b[0] === 255 && b[1] === 216 && b[2] === 255) return "image/jpeg"; if (b[0] === 71 && b[1] === 73 && b[2] === 70 && b[3] === 56) return "image/gif"; if (b[0] === 37 && b[1] === 80 && b[2] === 68 && b[3] === 70) return "application/pdf"; if (b.length >= 12 && b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80) return "image/webp"; if (b.length >= 12 && b[4] === 102 && b[5] === 116 && b[6] === 121 && b[7] === 112) { if (b[8] === 113 && b[9] === 116 && b[10] === 32) return "video/quicktime"; return "video/mp4"; } if (b[0] === 26 && b[1] === 69 && b[2] === 223 && b[3] === 163) return "video/webm"; if (b[0] === 73 && b[1] === 68 && b[2] === 51) return "audio/mpeg"; if (b[0] === 255 && (b[1] & 224) === 224) return "audio/mpeg"; if (b.length >= 12 && b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 65 && b[10] === 86 && b[11] === 69) return "audio/wav"; if (b[0] === 79 && b[1] === 103 && b[2] === 103 && b[3] === 83) return "audio/ogg"; if (b[0] === 102 && b[1] === 76 && b[2] === 97 && b[3] === 67) return "audio/flac"; return void 0; } const MIME_BY_EXT = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", avif: "image/avif", heic: "image/heic", svg: "image/svg+xml", mp4: "video/mp4", mov: "video/quicktime", webm: "video/webm", mkv: "video/x-matroska", mp3: "audio/mpeg", wav: "audio/wav", ogg: "audio/ogg", m4a: "audio/mp4", flac: "audio/flac", pdf: "application/pdf" }; function mimeFromExtension(path) { const i = path.lastIndexOf("."); if (i < 0) return void 0; const ext = path.slice(i + 1).toLowerCase(); return MIME_BY_EXT[ext]; } const DEFAULTS = { maxBytes: 64 * 1024 * 1024, maxDepth: 6, maxCandidates: 8, maxFileAgeMs: 30 * 60 * 1e3 // 30 min — older than a turn, probably stale }; const BASE64ISH = /^[A-Za-z0-9+/=_-]{800,}$/; function looksLikePath(s) { return s.length > 1 && s.length < 4096 && path.isAbsolute(s) && !s.includes("\n"); } function looksLikeUrl(s) { return /^https?:\/\/[^\s]+$/i.test(s) && s.length < 2048; } function extractPathsFromProse(text) { if (!text || text.length > 1e5) return []; const out = []; const re = /\/[^\s'"`)\]]+\.(?:png|jpe?g|gif|webp|svg|avif|heic|mp4|mov|webm|mkv|mp3|wav|ogg|m4a|flac|pdf)\b/gi; let m; while ((m = re.exec(text)) !== null) { out.push(m[0]); if (out.length >= 4) break; } return out; } async function scanToolResultForMedia(content, onMedia, options = {}) { const opts = { ...DEFAULTS, ...options }; const seenBytes = /* @__PURE__ */ new Set(); let emitted = 0; const tryEmit = async (cand) => { if (emitted >= opts.maxCandidates) return; if (cand.bytes.length === 0 || cand.bytes.length > opts.maxBytes) return; const key = `${cand.mime}|${cand.bytes.length}`; if (seenBytes.has(key)) return; seenBytes.add(key); emitted++; try { await onMedia(cand); } catch (err) { persistence.logger.debug("[mediaScan] onMedia callback threw:", err); } }; const tryEmitFromPath = async (path, name) => { try { const stat = await fs.promises.stat(path); if (!stat.isFile()) return; if (stat.size === 0 || stat.size > opts.maxBytes) return; if (Date.now() - stat.mtimeMs > opts.maxFileAgeMs) return; const extMime = mimeFromExtension(path); if (!extMime) return; const buf = await fs.promises.readFile(path); const sniffed = sniffMimeFromBytes(buf) ?? extMime; const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); await tryEmit({ bytes, mime: sniffed, name: name ?? basenameOf(path) }); } catch (err) { persistence.logger.debug(`[mediaScan] path read failed (${path}):`, err instanceof Error ? err.message : err); } }; const tryEmitFromUrl = async (url, name) => { try { const res = await fetch(url, { method: "GET" }); if (!res.ok) return; const ct = res.headers.get("content-type") ?? ""; const len = Number(res.headers.get("content-length") ?? "0"); if (len > opts.maxBytes) return; const ab = await res.arrayBuffer(); const buf = Buffer.from(ab); if (buf.length === 0 || buf.length > opts.maxBytes) return; const sniffed = sniffMimeFromBytes(buf); const isMediaCt = ct.startsWith("image/") || ct.startsWith("video/") || ct.startsWith("audio/") || ct === "application/pdf"; if (!sniffed && !isMediaCt) return; const mime = sniffed ?? ct.split(";")[0].trim(); const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); await tryEmit({ bytes, mime, name: name ?? url.split("/").pop() }); } catch (err) { persistence.logger.debug(`[mediaScan] url fetch failed (${url}):`, err instanceof Error ? err.message : err); } }; const walk = async (node, depth, hintName) => { if (emitted >= opts.maxCandidates) return; if (node === null || node === void 0 || depth > opts.maxDepth) return; if (typeof node === "string") { const s = node; if (looksLikePath(s)) { await tryEmitFromPath(s, hintName); return; } if (looksLikeUrl(s)) { await tryEmitFromUrl(s, hintName); return; } if (BASE64ISH.test(s)) { try { const buf = Buffer.from(s, "base64"); if (buf.length > 0) { const sniffed = sniffMimeFromBytes(buf); if (sniffed) { const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); await tryEmit({ bytes, mime: sniffed, name: hintName }); } } } catch { } return; } for (const p of extractPathsFromProse(s)) { await tryEmitFromPath(p); if (emitted >= opts.maxCandidates) return; } return; } if (Array.isArray(node)) { for (const item of node) { await walk(item, depth + 1); if (emitted >= opts.maxCandidates) return; } return; } if (typeof node === "object") { const o = node; if (typeof o.type === "string") { if (o.type === "image" && typeof o.data === "string") { const mime = (typeof o.mimeType === "string" ? o.mimeType : void 0) ?? "image/png"; try { const buf = Buffer.from(o.data, "base64"); if (buf.length > 0) { const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); await tryEmit({ bytes, mime, name: typeof o.name === "string" ? o.name : void 0 }); } } catch { } return; } if (o.type === "audio" && typeof o.data === "string") { const mime = (typeof o.mimeType === "string" ? o.mimeType : void 0) ?? "audio/mpeg"; try { const buf = Buffer.from(o.data, "base64"); if (buf.length > 0) { const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); await tryEmit({ bytes, mime, name: typeof o.name === "string" ? o.name : void 0 }); } } catch { } return; } if (o.type === "resource" && o.resource && typeof o.resource === "object") { const r = o.resource; if (typeof r.blob === "string") { const mime = (typeof r.mimeType === "string" ? r.mimeType : void 0) ?? "application/octet-stream"; try { const buf = Buffer.from(r.blob, "base64"); if (buf.length > 0) { const sniffed = sniffMimeFromBytes(buf) ?? mime; const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); await tryEmit({ bytes, mime: sniffed, name: typeof r.uri === "string" ? basenameOf(r.uri) : void 0 }); } } catch { } return; } if (typeof r.uri === "string") { const uri = r.uri; if (uri.startsWith("file://")) { await tryEmitFromPath(uri.slice(7)); return; } } } } for (const [k, v] of Object.entries(o)) { const lk = k.toLowerCase(); const hint = (lk.includes("name") || lk.includes("filename")) && typeof o.name === "string" ? o.name : void 0; await walk(v, depth + 1, hint); if (emitted >= opts.maxCandidates) return; } } }; await walk(content, 0); } function basenameOf(p) { const i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\")); return i >= 0 ? p.slice(i + 1) : p; } function resolveModelFromMeta(meta, currentModel) { if (!meta || !Object.prototype.hasOwnProperty.call(meta, "model")) { return { model: currentModel, reset: false, changed: false }; } if (meta.model === null) { return { model: void 0, reset: true, changed: currentModel !== void 0 }; } if (meta.model) { return { model: meta.model, reset: false, changed: currentModel !== meta.model }; } return { model: currentModel, reset: false, changed: false }; } exports.resolveModelFromMeta = resolveModelFromMeta; exports.scanToolResultForMedia = scanToolResultForMedia;