mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document browser utility helpers
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Rate-limit message selection for Browser service providers.
|
||||
*/
|
||||
const BROWSER_SERVICE_RATE_LIMIT_MESSAGE =
|
||||
"Browser service rate limit reached. " +
|
||||
"Wait for the current session to complete, or retry later.";
|
||||
@@ -22,6 +25,7 @@ function isBrowserbaseUrl(url: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the provider-specific rate-limit message for a browser service URL. */
|
||||
export function resolveBrowserRateLimitMessage(url: string): string {
|
||||
return isBrowserbaseUrl(url)
|
||||
? BROWSERBASE_RATE_LIMIT_MESSAGE
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Request policy helpers for profile-aware Browser control server routes.
|
||||
*/
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type BrowserRequestProfileParams = {
|
||||
@@ -6,6 +9,7 @@ type BrowserRequestProfileParams = {
|
||||
profile?: string | null;
|
||||
};
|
||||
|
||||
/** Normalizes route paths so mutation-policy checks compare stable slash forms. */
|
||||
export function normalizeBrowserRequestPath(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
@@ -18,6 +22,7 @@ export function normalizeBrowserRequestPath(value: string): string {
|
||||
return withLeadingSlash.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
/** Returns true when a control request mutates persistent browser profile state. */
|
||||
export function isPersistentBrowserProfileMutation(method: string, path: string): boolean {
|
||||
const normalizedPath = normalizeBrowserRequestPath(path);
|
||||
if (
|
||||
@@ -29,6 +34,7 @@ export function isPersistentBrowserProfileMutation(method: string, path: string)
|
||||
return method === "DELETE" && /^\/profiles\/[^/]+$/.test(normalizedPath);
|
||||
}
|
||||
|
||||
/** Resolves the requested profile from query, body, or route defaults. */
|
||||
export function resolveRequestedBrowserProfile(
|
||||
params: BrowserRequestProfileParams,
|
||||
): string | undefined {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Runtime config refresh helpers for Browser profiles that can be hot-reloaded
|
||||
* without restarting the whole Browser plugin server.
|
||||
*/
|
||||
import { loadBrowserConfigForRuntimeRefresh } from "./config-refresh-source.js";
|
||||
import { resolveBrowserConfig, resolveProfile, type ResolvedBrowserProfile } from "./config.js";
|
||||
import type { BrowserServerState } from "./server-context.types.js";
|
||||
@@ -82,6 +86,7 @@ function applyResolvedConfig(
|
||||
}
|
||||
}
|
||||
|
||||
/** Refreshes the Browser runtime's resolved config from disk when hot reload is enabled. */
|
||||
export function refreshResolvedBrowserConfigFromDisk(params: {
|
||||
current: BrowserServerState;
|
||||
refreshConfigFromDisk: boolean;
|
||||
@@ -98,6 +103,7 @@ export function refreshResolvedBrowserConfigFromDisk(params: {
|
||||
applyResolvedConfig(params.current, freshResolved);
|
||||
}
|
||||
|
||||
/** Resolves a profile after an optional cached/fresh config reload. */
|
||||
export function resolveBrowserProfileWithHotReload(params: {
|
||||
current: BrowserServerState;
|
||||
refreshConfigFromDisk: boolean;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Browser plugin runtime lifecycle helpers for startup relay setup and shutdown
|
||||
* cleanup.
|
||||
*/
|
||||
import type { Server } from "node:http";
|
||||
import { getPwAiModule } from "./pw-ai-module.js";
|
||||
import { isPwAiLoaded } from "./pw-ai-state.js";
|
||||
@@ -6,6 +10,7 @@ import { ensureExtensionRelayForProfiles, stopKnownBrowserProfiles } from "./ser
|
||||
import { startTrackedBrowserTabCleanupTimer } from "./session-tab-cleanup.js";
|
||||
import { registerBrowserUnhandledRejectionHandler } from "./unhandled-rejections.js";
|
||||
|
||||
/** Creates Browser server state and starts runtime-wide cleanup handlers. */
|
||||
export async function createBrowserRuntimeState(params: {
|
||||
resolved: BrowserServerState["resolved"];
|
||||
port: number;
|
||||
@@ -31,6 +36,7 @@ export async function createBrowserRuntimeState(params: {
|
||||
return state;
|
||||
}
|
||||
|
||||
/** Stops Browser profiles, the optional HTTP server, and loaded Playwright state. */
|
||||
export async function stopBrowserRuntime(params: {
|
||||
current: BrowserServerState | null;
|
||||
getState: () => BrowserServerState | null;
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
/**
|
||||
* Re-export for Browser download filename sanitization.
|
||||
*/
|
||||
export { sanitizeUntrustedFileName } from "../sdk-security-runtime.js";
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Browser screenshot normalization helpers that bound screenshots for media
|
||||
* transport and model input.
|
||||
*/
|
||||
import {
|
||||
buildImageResizeSideGrid,
|
||||
getImageMetadata,
|
||||
@@ -9,6 +13,7 @@ import {
|
||||
export const DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE = 2000;
|
||||
export const DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
||||
|
||||
/** Downscales/re-encodes screenshots to fit Browser plugin byte and dimension caps. */
|
||||
export async function normalizeBrowserScreenshot(
|
||||
buffer: Buffer,
|
||||
opts?: {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
/**
|
||||
* Helpers for appending discovered page links to text snapshots.
|
||||
*/
|
||||
/** Link metadata appended to Browser page snapshots. */
|
||||
export type SnapshotUrlEntry = {
|
||||
text: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
/** Appends a compact numbered link list to a snapshot string. */
|
||||
export function appendSnapshotUrls(snapshot: string, urls: readonly SnapshotUrlEntry[]): string {
|
||||
if (urls.length === 0) {
|
||||
return snapshot;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* SSRF policy helpers for Browser routes that need one-off hostname grants.
|
||||
*/
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
|
||||
/** Returns an SSRF policy with the hostname added to allowedHostnames. */
|
||||
export function withAllowedHostname(
|
||||
ssrfPolicy: SsrFPolicy | undefined,
|
||||
hostname: string,
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
/**
|
||||
* Target id resolution helpers for Browser tab aliases and user-facing ids.
|
||||
*/
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
/** Result for resolving a user-supplied tab id, label, or target prefix. */
|
||||
type TargetIdResolution =
|
||||
| { ok: true; targetId: string }
|
||||
| { ok: false; reason: "not_found" | "ambiguous"; matches?: string[] };
|
||||
|
||||
/** Resolves exact tab ids/labels first, then unique target-id prefixes. */
|
||||
export function resolveTargetIdFromTabs(
|
||||
input: string,
|
||||
tabs: Array<{ targetId: string; suggestedTargetId?: string; tabId?: string; label?: string }>,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* Test helper for reserving a loopback port for Browser control server tests.
|
||||
*/
|
||||
import { createServer } from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
|
||||
/** Returns an available 127.0.0.1 TCP port. */
|
||||
export async function getFreePort(): Promise<number> {
|
||||
while (true) {
|
||||
const port = await new Promise<number>((resolve, reject) => {
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
/**
|
||||
* Test fetch resolver that bypasses mocked global fetch when Browser tests need
|
||||
* a real HTTP client.
|
||||
*/
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
type FetchLike = ((input: string | URL, init?: RequestInit) => Promise<Response>) & {
|
||||
mock?: unknown;
|
||||
};
|
||||
|
||||
/** Fetch shape used by Browser integration test helpers. */
|
||||
export type BrowserTestFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
||||
|
||||
function isUsableFetch(value: unknown): value is FetchLike {
|
||||
return typeof value === "function" && !("mock" in (value as FetchLike));
|
||||
}
|
||||
|
||||
/** Returns undici fetch when usable, falling back to an unmocked global fetch. */
|
||||
export function getBrowserTestFetch(): BrowserTestFetch {
|
||||
const require = createRequire(import.meta.url);
|
||||
const vitest = (globalThis as { vi?: { doUnmock?: (id: string) => void } }).vi;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/**
|
||||
* Timer delay normalization for Browser waits and cleanup loops.
|
||||
*/
|
||||
/** Largest timeout delay accepted reliably by Node timers. */
|
||||
export const MAX_SAFE_TIMEOUT_DELAY_MS = 2_147_483_647;
|
||||
|
||||
/** Clamps timer delays to Node's safe range with an optional lower bound. */
|
||||
export function normalizeBrowserTimerDelayMs(timeoutMs: number, opts?: { minMs?: number }): number {
|
||||
const rawMinMs = opts?.minMs ?? 1;
|
||||
const minMs = Math.min(
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
/**
|
||||
* Trash helpers for Browser-owned files constrained to user and OpenClaw temp
|
||||
* roots.
|
||||
*/
|
||||
import os from "node:os";
|
||||
import { movePathToTrash as movePathToTrashWithAllowedRoots } from "openclaw/plugin-sdk/browser-config";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
|
||||
/** Moves a path to trash only when it lives under allowed Browser roots. */
|
||||
export async function movePathToTrash(targetPath: string): Promise<string> {
|
||||
return await movePathToTrashWithAllowedRoots(targetPath, {
|
||||
allowedRoots: [os.homedir(), resolvePreferredOpenClawTmpDir()],
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Browser-specific unhandled rejection filter for benign Playwright dialog
|
||||
* races.
|
||||
*/
|
||||
import { collectErrorGraphCandidates } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { registerUnhandledRejectionHandler } from "openclaw/plugin-sdk/runtime-env";
|
||||
|
||||
@@ -27,6 +31,7 @@ function readPlaywrightMethod(err: unknown): string | undefined {
|
||||
return typeof method === "string" ? method : undefined;
|
||||
}
|
||||
|
||||
/** Detects Playwright "no dialog is showing" races that can escape as rejections. */
|
||||
export function isPlaywrightDialogRaceUnhandledRejection(reason: unknown): boolean {
|
||||
for (const candidate of collectErrorGraphCandidates(reason, (current) => [
|
||||
current.cause,
|
||||
@@ -56,6 +61,7 @@ export function isPlaywrightDialogRaceUnhandledRejection(reason: unknown): boole
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Installs the Browser unhandled-rejection filter and returns its disposer. */
|
||||
export function registerBrowserUnhandledRejectionHandler(): () => void {
|
||||
return registerUnhandledRejectionHandler(isPlaywrightDialogRaceUnhandledRejection);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* URL pattern matching for Browser response and wait tools.
|
||||
*/
|
||||
function wildcardPatternToRegExp(pattern: string): RegExp {
|
||||
let source = "^";
|
||||
for (let index = 0; index < pattern.length; index += 1) {
|
||||
@@ -17,6 +20,7 @@ function wildcardPatternToRegExp(pattern: string): RegExp {
|
||||
return new RegExp(source, "u");
|
||||
}
|
||||
|
||||
/** Matches exact, wildcard, or substring URL patterns against a browser URL. */
|
||||
export function matchBrowserUrlPattern(pattern: string, url: string): boolean {
|
||||
const trimmedPattern = pattern.trim();
|
||||
if (!trimmedPattern) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Browser screenshot descriptions piggyback on the existing media image
|
||||
// understanding contract. No browser-specific model registry lives here.
|
||||
/**
|
||||
* Browser screenshot description helpers built on the shared media image
|
||||
* understanding contract. No browser-specific model registry lives here.
|
||||
*/
|
||||
|
||||
import { readFile } from "node:fs/promises";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
@@ -7,9 +9,11 @@ import type { describeImageFile as DescribeImageFileFn } from "openclaw/plugin-s
|
||||
import type { saveMediaBuffer as SaveMediaBufferFn } from "../sdk-setup-tools.js";
|
||||
import type { normalizeBrowserScreenshot as NormalizeBrowserScreenshotFn } from "./screenshot.js";
|
||||
|
||||
/** Default prompt for turning browser screenshots into text-only page context. */
|
||||
export const DEFAULT_BROWSER_SCREENSHOT_DESCRIPTION_PROMPT =
|
||||
"Describe what is visible in this browser screenshot. Capture page layout, headings, primary content blocks, visible text, and notable interactive elements so a text-only assistant can reason about the page.";
|
||||
|
||||
/** Input context for browser screenshot image understanding. */
|
||||
export type BrowserScreenshotDescriptionContext = {
|
||||
cfg: OpenClawConfig;
|
||||
filePath: string;
|
||||
@@ -30,12 +34,14 @@ export type BrowserScreenshotDescriptionContext = {
|
||||
};
|
||||
};
|
||||
|
||||
/** Dependencies injected so Browser tests can avoid loading media runtimes. */
|
||||
export type BrowserScreenshotDescriptionDeps = {
|
||||
describeImageFile: typeof DescribeImageFileFn;
|
||||
normalizeBrowserScreenshot: typeof NormalizeBrowserScreenshotFn;
|
||||
saveMediaBuffer: typeof SaveMediaBufferFn;
|
||||
};
|
||||
|
||||
/** Result returned from browser screenshot description. */
|
||||
export type BrowserScreenshotDescriptionResult = {
|
||||
text: string;
|
||||
provider?: string;
|
||||
@@ -78,6 +84,7 @@ async function resolveImageUnderstandingFilePath(
|
||||
return saved.path;
|
||||
}
|
||||
|
||||
/** Produces a text description for a browser screenshot, or null when no text was produced. */
|
||||
export async function describeBrowserScreenshot(
|
||||
ctx: BrowserScreenshotDescriptionContext,
|
||||
deps: BrowserScreenshotDescriptionDeps,
|
||||
@@ -104,6 +111,7 @@ export async function describeBrowserScreenshot(
|
||||
};
|
||||
}
|
||||
|
||||
/** Neutralizes model-generated MEDIA directives before feeding text back to tools. */
|
||||
export function neutralizeMediaDirectives(text: string): string {
|
||||
if (!text || !/media:/i.test(text)) {
|
||||
return text;
|
||||
|
||||
Reference in New Issue
Block a user