fix(tool): support top-level await in the browser eval action

This commit is contained in:
matevip 2026-05-19 16:53:18 +08:00
parent c6d51f8ced
commit d1b7e76f5b

View File

@ -26,6 +26,7 @@ import java.nio.file.Paths;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.*;
import java.util.regex.Pattern;
/**
* 浏览器自动化工具
@ -98,7 +99,7 @@ public class BrowserUseTool {
- screenshot: Take a screenshot. Optional path to save file; returns base64 if no path.
- click: Click an element. Requires selector (CSS selector).
- type: Type text into an element. Requires selector and text.
- eval: Execute JavaScript on the page. Requires code parameter.
- eval: Execute JavaScript on the page. Requires code parameter. Top-level await is supported; use `return` to surface a value.
- connect_cdp: Connect to an existing Chrome via CDP. Requires url (e.g. "http://localhost:9222").
- list_cdp_targets: Scan local ports (9000-10000) for CDP endpoints. Optional cdpPort for single port.
- navigate_back: Go back in browser history.
@ -109,7 +110,7 @@ public class BrowserUseTool {
@ToolParam(description = "URL to navigate to (for open), or CDP base URL (for connect_cdp, e.g. http://localhost:9222)", required = false) String url,
@ToolParam(description = "CSS selector for target element (for click/type)", required = false) String selector,
@ToolParam(description = "Text to type (for action=type)", required = false) String text,
@ToolParam(description = "JavaScript code to execute (for action=eval)", required = false) String code,
@ToolParam(description = "JavaScript code to execute (for action=eval). Top-level await is allowed; add `return` to return a value when the snippet uses await.", required = false) String code,
@ToolParam(description = "File path to save screenshot (for action=screenshot)", required = false) String path,
@ToolParam(description = "Launch visible browser window (for action=start, default false)", required = false) Boolean headed,
@ToolParam(description = "Single CDP port to scan (for action=list_cdp_targets)", required = false) Integer cdpPort,
@ -642,6 +643,9 @@ public class BrowserUseTool {
return JSONUtil.toJsonPrettyStr(result);
}
/** Detects the {@code await} keyword as a whole word to decide whether eval code needs an async wrapper. */
private static final Pattern TOP_LEVEL_AWAIT = Pattern.compile("\\bawait\\b");
private String doEval(String sessionKey, String code) {
if (code == null || code.isBlank()) {
return error("code is required for action=eval");
@ -655,7 +659,13 @@ public class BrowserUseTool {
session.touch();
Page page = session.page;
Object evalResult = page.evaluate(code);
// Playwright evaluates the supplied string as a plain expression, which
// rejects top-level `await`. Wrap such snippets in an async IIFE so the
// agent can await promises; Playwright auto-resolves the returned one.
String script = TOP_LEVEL_AWAIT.matcher(code).find()
? "(async () => {" + code + "})()"
: code;
Object evalResult = page.evaluate(script);
String resultStr = evalResult != null ? evalResult.toString() : "null";
if (resultStr.length() > 10_000) {