import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; import { BROWSER_REQUEST_GATEWAY_METHOD, BROWSER_REQUEST_GATEWAY_SCOPES, } from "../browser-gateway-contract.js"; import { callGatewayFromCli, type GatewayRpcOpts } from "./core-api.js"; const MAX_SAFE_TIMEOUT_DELAY_MS = 2_147_483_647; export type BrowserParentOpts = GatewayRpcOpts & { json?: boolean; browserProfile?: string; }; type BrowserRequestParams = { method: "GET" | "POST" | "DELETE"; path: string; query?: Record; body?: unknown; }; function normalizeQuery(query: BrowserRequestParams["query"]): Record | undefined { if (!query) { return undefined; } const out: Record = {}; for (const [key, value] of Object.entries(query)) { if (value === undefined) { continue; } out[key] = String(value); } return Object.keys(out).length ? out : undefined; } function parsePositiveInteger(raw: string, flag: string): number { const value = raw.trim(); if (!/^\+?\d+$/.test(value)) { throw new Error(`${flag} must be a positive integer.`); } const parsed = Number(value); if (!Number.isSafeInteger(parsed) || parsed <= 0) { throw new Error(`${flag} must be a positive integer.`); } return parsed; } function normalizeCliTimeoutMs(timeoutMs: number): number { return Math.min(MAX_SAFE_TIMEOUT_DELAY_MS, Math.max(1, Math.floor(timeoutMs))); } export async function callBrowserRequest( opts: BrowserParentOpts, params: BrowserRequestParams, extra?: { timeoutMs?: number; progress?: boolean }, ): Promise { const resolvedTimeoutMs = typeof extra?.timeoutMs === "number" && Number.isFinite(extra.timeoutMs) ? normalizeCliTimeoutMs(extra.timeoutMs) : typeof opts.timeout === "string" ? normalizeCliTimeoutMs(parsePositiveInteger(opts.timeout, "--timeout")) : undefined; const resolvedTimeout = typeof resolvedTimeoutMs === "number" && Number.isFinite(resolvedTimeoutMs) ? resolvedTimeoutMs : undefined; const timeout = typeof resolvedTimeout === "number" ? String(resolvedTimeout) : opts.timeout; const payload = await callGatewayFromCli( BROWSER_REQUEST_GATEWAY_METHOD, { ...opts, timeout }, { method: params.method, path: params.path, query: normalizeQuery(params.query), body: params.body, timeoutMs: resolvedTimeout, }, { progress: extra?.progress, scopes: [...BROWSER_REQUEST_GATEWAY_SCOPES] }, ); if (payload === undefined) { throw new Error("Unexpected browser.request response"); } return payload as T; } export async function callBrowserResize( opts: BrowserParentOpts, params: { profile?: string; width: number; height: number; targetId?: string }, extra?: { timeoutMs?: number }, ): Promise { return callBrowserRequest( opts, { method: "POST", path: "/act", query: params.profile ? { profile: params.profile } : undefined, body: { kind: "resize", width: params.width, height: params.height, targetId: normalizeOptionalString(params.targetId), }, }, extra, ); }