UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

149 lines • 5.79 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import path from 'node:path'; import { lighthouseRunner, generateReport, zod, } from '../third_party/index.js'; import { ToolCategory } from './categories.js'; import { startTrace } from './performance.js'; import { definePageTool } from './ToolDefinition.js'; // Kept in sync with `constants.userAgents` in Lighthouse's // core/config/constants.js, which `lighthouse:default` and the `desktop` // preset use. The bundle in src/third_party only re-exports the runner // entrypoints, so these cannot be imported and are mirrored here alongside the // screen emulation metrics below. Refresh via scripts/update-lighthouse.ts. const MOBILE_USER_AGENT = 'Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36'; const DESKTOP_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36'; export const lighthouseAudit = definePageTool((args) => ({ name: 'lighthouse_audit', description: `Get Lighthouse score and reports for accessibility, SEO, best practices, and agentic browsing. This excludes performance. For performance audits, run ${startTrace(args).name}`, annotations: { category: ToolCategory.DEBUGGING, readOnlyHint: false, }, schema: { mode: zod .enum(['navigation', 'snapshot']) .default('navigation') .describe('"navigation" reloads & audits. "snapshot" analyzes current state.'), device: zod .enum(['desktop', 'mobile']) .default('desktop') .describe('Device to emulate.'), outputDirPath: zod .string() .optional() .describe('Directory for reports. If omitted, uses temporary files.'), }, blockedByDialog: true, verifyFilesSchema: { outputDirPath: true, }, handler: async (request, response, context) => { const page = request.page; const categories = [ 'accessibility', 'seo', 'best-practices', 'agentic-browsing', ]; const formats = ['json', 'html']; const { mode = 'navigation', device = 'desktop', outputDirPath, } = request.params; const flags = { onlyCategories: categories, output: formats, // Default 30 second timeout for page load. maxWaitForLoad: 30_000, }; if (device === 'desktop') { flags.formFactor = 'desktop'; flags.screenEmulation = { mobile: false, width: 1350, height: 940, deviceScaleFactor: 1, disabled: false, }; flags.emulatedUserAgent = DESKTOP_USER_AGENT; } else { flags.formFactor = 'mobile'; flags.screenEmulation = { mobile: true, width: 412, height: 823, deviceScaleFactor: 1.75, disabled: false, }; flags.emulatedUserAgent = MOBILE_USER_AGENT; } let result; try { if (mode === 'navigation') { result = await lighthouseRunner.navigation(page.pptrPage, page.pptrPage.url(), { flags, }); } else { result = await lighthouseRunner.snapshot(page.pptrPage, { flags, }); } if (!result) { throw new Error('Lighthouse audit failed.'); } } finally { await page.restoreEmulation(); } const lhr = result.lhr; const reportPaths = []; const encoder = new TextEncoder(); const savePromises = formats.map(async (format) => { const report = generateReport(lhr, format); const data = encoder.encode(report); if (outputDirPath) { const reportPath = path.join(outputDirPath, `report`); const { filename } = await context.saveFile(data, reportPath, `.${format}`); return filename; } const { filepath } = await context.saveTemporaryFile(data, `report.${format}`); return filepath; }); const results = await Promise.allSettled(savePromises); for (const res of results) { if (res.status === 'rejected') { throw res.reason; } reportPaths.push(res.value); } const categoryScores = Object.values(lhr.categories).map(c => ({ id: c.id, title: c.title, score: c.score, })); const failedAudits = Object.values(lhr.audits).filter(a => a.score !== null && a.score < 1).length; const passedAudits = Object.values(lhr.audits).filter(a => a.score === 1).length; const output = { summary: { mode, device, // `mainDocumentUrl` is only set for navigations, whereas // `finalDisplayedUrl` is populated for every gather mode. url: lhr.finalDisplayedUrl, scores: categoryScores, audits: { failed: failedAudits, passed: passedAudits, }, timing: { total: lhr.timing.total, }, }, reports: reportPaths, }; response.attachLighthouseResult(output); }, })); //# sourceMappingURL=lighthouse.js.map