refactor(browser): reuse shared route context in agent act routes

This commit is contained in:
Peter Steinberger
2026-02-18 22:01:28 +00:00
parent b76e19ceb7
commit b30e3467ee

View File

@@ -7,10 +7,9 @@ import {
parseClickModifiers, parseClickModifiers,
} from "./agent.act.shared.js"; } from "./agent.act.shared.js";
import { import {
handleRouteError,
readBody, readBody,
requirePwAi, resolveTargetIdFromBody,
resolveProfileContext, withPlaywrightRouteContext,
SELECTOR_UNSUPPORTED_MESSAGE, SELECTOR_UNSUPPORTED_MESSAGE,
} from "./agent.shared.js"; } from "./agent.shared.js";
import { import {
@@ -27,28 +26,24 @@ export function registerBrowserAgentActRoutes(
ctx: BrowserRouteContext, ctx: BrowserRouteContext,
) { ) {
app.post("/act", async (req, res) => { app.post("/act", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) {
return;
}
const body = readBody(req); const body = readBody(req);
const kindRaw = toStringOrEmpty(body.kind); const kindRaw = toStringOrEmpty(body.kind);
if (!isActKind(kindRaw)) { if (!isActKind(kindRaw)) {
return jsonError(res, 400, "kind is required"); return jsonError(res, 400, "kind is required");
} }
const kind: ActKind = kindRaw; const kind: ActKind = kindRaw;
const targetId = toStringOrEmpty(body.targetId) || undefined; const targetId = resolveTargetIdFromBody(body);
if (Object.hasOwn(body, "selector") && kind !== "wait") { if (Object.hasOwn(body, "selector") && kind !== "wait") {
return jsonError(res, 400, SELECTOR_UNSUPPORTED_MESSAGE); return jsonError(res, 400, SELECTOR_UNSUPPORTED_MESSAGE);
} }
try { await withPlaywrightRouteContext({
const tab = await profileCtx.ensureTabAvailable(targetId); req,
const cdpUrl = profileCtx.profile.cdpUrl; res,
const pw = await requirePwAi(res, `act:${kind}`); ctx,
if (!pw) { targetId,
return; feature: `act:${kind}`,
} run: async ({ cdpUrl, tab, pw }) => {
const evaluateEnabled = ctx.state().resolved.evaluateEnabled; const evaluateEnabled = ctx.state().resolved.evaluateEnabled;
switch (kind) { switch (kind) {
@@ -339,18 +334,13 @@ export function registerBrowserAgentActRoutes(
return jsonError(res, 400, "unsupported kind"); return jsonError(res, 400, "unsupported kind");
} }
} }
} catch (err) { },
handleRouteError(ctx, res, err); });
}
}); });
app.post("/hooks/file-chooser", async (req, res) => { app.post("/hooks/file-chooser", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) {
return;
}
const body = readBody(req); const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined; const targetId = resolveTargetIdFromBody(body);
const ref = toStringOrEmpty(body.ref) || undefined; const ref = toStringOrEmpty(body.ref) || undefined;
const inputRef = toStringOrEmpty(body.inputRef) || undefined; const inputRef = toStringOrEmpty(body.inputRef) || undefined;
const element = toStringOrEmpty(body.element) || undefined; const element = toStringOrEmpty(body.element) || undefined;
@@ -359,7 +349,14 @@ export function registerBrowserAgentActRoutes(
if (!paths.length) { if (!paths.length) {
return jsonError(res, 400, "paths are required"); return jsonError(res, 400, "paths are required");
} }
try {
await withPlaywrightRouteContext({
req,
res,
ctx,
targetId,
feature: "file chooser hook",
run: async ({ cdpUrl, tab, pw }) => {
const uploadPathsResult = resolvePathsWithinRoot({ const uploadPathsResult = resolvePathsWithinRoot({
rootDir: DEFAULT_UPLOAD_DIR, rootDir: DEFAULT_UPLOAD_DIR,
requestedPaths: paths, requestedPaths: paths,
@@ -371,17 +368,12 @@ export function registerBrowserAgentActRoutes(
} }
const resolvedPaths = uploadPathsResult.paths; const resolvedPaths = uploadPathsResult.paths;
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "file chooser hook");
if (!pw) {
return;
}
if (inputRef || element) { if (inputRef || element) {
if (ref) { if (ref) {
return jsonError(res, 400, "ref cannot be combined with inputRef/element"); return jsonError(res, 400, "ref cannot be combined with inputRef/element");
} }
await pw.setInputFilesViaPlaywright({ await pw.setInputFilesViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl, cdpUrl,
targetId: tab.targetId, targetId: tab.targetId,
inputRef, inputRef,
element, element,
@@ -389,72 +381,66 @@ export function registerBrowserAgentActRoutes(
}); });
} else { } else {
await pw.armFileUploadViaPlaywright({ await pw.armFileUploadViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl, cdpUrl,
targetId: tab.targetId, targetId: tab.targetId,
paths: resolvedPaths, paths: resolvedPaths,
timeoutMs: timeoutMs ?? undefined, timeoutMs: timeoutMs ?? undefined,
}); });
if (ref) { if (ref) {
await pw.clickViaPlaywright({ await pw.clickViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl, cdpUrl,
targetId: tab.targetId, targetId: tab.targetId,
ref, ref,
}); });
} }
} }
res.json({ ok: true }); res.json({ ok: true });
} catch (err) { },
handleRouteError(ctx, res, err); });
}
}); });
app.post("/hooks/dialog", async (req, res) => { app.post("/hooks/dialog", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) {
return;
}
const body = readBody(req); const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined; const targetId = resolveTargetIdFromBody(body);
const accept = toBoolean(body.accept); const accept = toBoolean(body.accept);
const promptText = toStringOrEmpty(body.promptText) || undefined; const promptText = toStringOrEmpty(body.promptText) || undefined;
const timeoutMs = toNumber(body.timeoutMs); const timeoutMs = toNumber(body.timeoutMs);
if (accept === undefined) { if (accept === undefined) {
return jsonError(res, 400, "accept is required"); return jsonError(res, 400, "accept is required");
} }
try {
const tab = await profileCtx.ensureTabAvailable(targetId); await withPlaywrightRouteContext({
const pw = await requirePwAi(res, "dialog hook"); req,
if (!pw) { res,
return; ctx,
} targetId,
feature: "dialog hook",
run: async ({ cdpUrl, tab, pw }) => {
await pw.armDialogViaPlaywright({ await pw.armDialogViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl, cdpUrl,
targetId: tab.targetId, targetId: tab.targetId,
accept, accept,
promptText, promptText,
timeoutMs: timeoutMs ?? undefined, timeoutMs: timeoutMs ?? undefined,
}); });
res.json({ ok: true }); res.json({ ok: true });
} catch (err) { },
handleRouteError(ctx, res, err); });
}
}); });
app.post("/wait/download", async (req, res) => { app.post("/wait/download", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) {
return;
}
const body = readBody(req); const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined; const targetId = resolveTargetIdFromBody(body);
const out = toStringOrEmpty(body.path) || ""; const out = toStringOrEmpty(body.path) || "";
const timeoutMs = toNumber(body.timeoutMs); const timeoutMs = toNumber(body.timeoutMs);
try {
const tab = await profileCtx.ensureTabAvailable(targetId); await withPlaywrightRouteContext({
const pw = await requirePwAi(res, "wait for download"); req,
if (!pw) { res,
return; ctx,
} targetId,
feature: "wait for download",
run: async ({ cdpUrl, tab, pw }) => {
let downloadPath: string | undefined; let downloadPath: string | undefined;
if (out.trim()) { if (out.trim()) {
const downloadPathResult = resolvePathWithinRoot({ const downloadPathResult = resolvePathWithinRoot({
@@ -469,24 +455,19 @@ export function registerBrowserAgentActRoutes(
downloadPath = downloadPathResult.path; downloadPath = downloadPathResult.path;
} }
const result = await pw.waitForDownloadViaPlaywright({ const result = await pw.waitForDownloadViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl, cdpUrl,
targetId: tab.targetId, targetId: tab.targetId,
path: downloadPath, path: downloadPath,
timeoutMs: timeoutMs ?? undefined, timeoutMs: timeoutMs ?? undefined,
}); });
res.json({ ok: true, targetId: tab.targetId, download: result }); res.json({ ok: true, targetId: tab.targetId, download: result });
} catch (err) { },
handleRouteError(ctx, res, err); });
}
}); });
app.post("/download", async (req, res) => { app.post("/download", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) {
return;
}
const body = readBody(req); const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined; const targetId = resolveTargetIdFromBody(body);
const ref = toStringOrEmpty(body.ref); const ref = toStringOrEmpty(body.ref);
const out = toStringOrEmpty(body.path); const out = toStringOrEmpty(body.path);
const timeoutMs = toNumber(body.timeoutMs); const timeoutMs = toNumber(body.timeoutMs);
@@ -496,7 +477,14 @@ export function registerBrowserAgentActRoutes(
if (!out) { if (!out) {
return jsonError(res, 400, "path is required"); return jsonError(res, 400, "path is required");
} }
try {
await withPlaywrightRouteContext({
req,
res,
ctx,
targetId,
feature: "download",
run: async ({ cdpUrl, tab, pw }) => {
const downloadPathResult = resolvePathWithinRoot({ const downloadPathResult = resolvePathWithinRoot({
rootDir: DEFAULT_DOWNLOAD_DIR, rootDir: DEFAULT_DOWNLOAD_DIR,
requestedPath: out, requestedPath: out,
@@ -506,81 +494,69 @@ export function registerBrowserAgentActRoutes(
res.status(400).json({ error: downloadPathResult.error }); res.status(400).json({ error: downloadPathResult.error });
return; return;
} }
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "download");
if (!pw) {
return;
}
const result = await pw.downloadViaPlaywright({ const result = await pw.downloadViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl, cdpUrl,
targetId: tab.targetId, targetId: tab.targetId,
ref, ref,
path: downloadPathResult.path, path: downloadPathResult.path,
timeoutMs: timeoutMs ?? undefined, timeoutMs: timeoutMs ?? undefined,
}); });
res.json({ ok: true, targetId: tab.targetId, download: result }); res.json({ ok: true, targetId: tab.targetId, download: result });
} catch (err) { },
handleRouteError(ctx, res, err); });
}
}); });
app.post("/response/body", async (req, res) => { app.post("/response/body", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) {
return;
}
const body = readBody(req); const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined; const targetId = resolveTargetIdFromBody(body);
const url = toStringOrEmpty(body.url); const url = toStringOrEmpty(body.url);
const timeoutMs = toNumber(body.timeoutMs); const timeoutMs = toNumber(body.timeoutMs);
const maxChars = toNumber(body.maxChars); const maxChars = toNumber(body.maxChars);
if (!url) { if (!url) {
return jsonError(res, 400, "url is required"); return jsonError(res, 400, "url is required");
} }
try {
const tab = await profileCtx.ensureTabAvailable(targetId); await withPlaywrightRouteContext({
const pw = await requirePwAi(res, "response body"); req,
if (!pw) { res,
return; ctx,
} targetId,
feature: "response body",
run: async ({ cdpUrl, tab, pw }) => {
const result = await pw.responseBodyViaPlaywright({ const result = await pw.responseBodyViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl, cdpUrl,
targetId: tab.targetId, targetId: tab.targetId,
url, url,
timeoutMs: timeoutMs ?? undefined, timeoutMs: timeoutMs ?? undefined,
maxChars: maxChars ?? undefined, maxChars: maxChars ?? undefined,
}); });
res.json({ ok: true, targetId: tab.targetId, response: result }); res.json({ ok: true, targetId: tab.targetId, response: result });
} catch (err) { },
handleRouteError(ctx, res, err); });
}
}); });
app.post("/highlight", async (req, res) => { app.post("/highlight", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) {
return;
}
const body = readBody(req); const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined; const targetId = resolveTargetIdFromBody(body);
const ref = toStringOrEmpty(body.ref); const ref = toStringOrEmpty(body.ref);
if (!ref) { if (!ref) {
return jsonError(res, 400, "ref is required"); return jsonError(res, 400, "ref is required");
} }
try {
const tab = await profileCtx.ensureTabAvailable(targetId); await withPlaywrightRouteContext({
const pw = await requirePwAi(res, "highlight"); req,
if (!pw) { res,
return; ctx,
} targetId,
feature: "highlight",
run: async ({ cdpUrl, tab, pw }) => {
await pw.highlightViaPlaywright({ await pw.highlightViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl, cdpUrl,
targetId: tab.targetId, targetId: tab.targetId,
ref, ref,
}); });
res.json({ ok: true, targetId: tab.targetId }); res.json({ ok: true, targetId: tab.targetId });
} catch (err) { },
handleRouteError(ctx, res, err); });
}
}); });
} }