browser-use upgrade patch test
frdel committed
Aug 27, 2025 at 20:45 UTC
439f2216ffc41265af974e7e534f03c6011e39b3
2 files changed
+70
-9
models.py
+58
-1
@@ -435,6 +435,52 @@ class BrowserCompatibleChatWrapper(ChatOpenRouter):
435
def get_client(self, *args, **kwargs): # type: ignore
436
return AsyncAIChatReplacement(self, *args, **kwargs)
437
438
+ # -- Gemini helper -----------------------------------------
439
+ def _gemini_clean_and_conform(self, text: str):
440
+ obj = None
441
+ try:
442
+ # dirty_json parser is robust enough to handle markdown fences
443
+ obj = dirty_json.parse(text)
444
+ except Exception:
445
+ return None # return None if parsing fails
446
+
447
+ if not isinstance(obj, dict):
448
+ return None
449
+
450
+ # Conform actions to browser-use expectations
451
+ if isinstance(obj.get("action"), list):
452
+ normalized_actions = []
453
+ for item in obj["action"]:
454
+ if not isinstance(item, dict):
455
+ continue # Skip non-dict items
456
+
457
+ action_key, action_value = next(iter(item.items()), (None, None))
458
+ if not action_key:
459
+ continue
460
+
461
+ # Create a mutable copy of the value
462
+ v = (action_value or {}).copy()
463
+
464
+ if action_key in ("scroll_down", "scroll_up", "scroll"):
465
+ is_down = action_key != "scroll_up"
466
+ v.setdefault("down", is_down)
467
+ v.setdefault("num_pages", 1.0)
468
+ normalized_actions.append({"scroll": v})
469
+ elif action_key == "go_to_url":
470
+ v.setdefault("new_tab", False)
471
+ normalized_actions.append({action_key: v})
472
+ elif action_key == "done":
473
+ if "text" in v and "data" not in v:
474
+ t = v.pop("text", "")
475
+ v["data"] = {"title": "Task result", "response": t, "page_summary": t}
476
+ v.setdefault("success", True)
477
+ normalized_actions.append({action_key: v})
478
+ else:
479
+ normalized_actions.append(item)
480
+ obj["action"] = normalized_actions
481
+
482
+ return dirty_json.stringify(obj)
483
+
484
async def _acall(
485
self,
486
messages: List[BaseMessage],
@@ -450,7 +496,7 @@ class BrowserCompatibleChatWrapper(ChatOpenRouter):
496
model = kwargs.pop("model", None)
497
kwrgs = {**self._wrapper.kwargs, **kwargs}
498
453
- # hack from browser-use to fix json schema for gemini
499
+ # hack from browser-use to fix json schema for gemini (additionalProperties, $defs, $ref)
500
if "response_format" in kwrgs and "json_schema" in kwrgs["response_format"] and model.startswith("gemini/"):
501
kwrgs["response_format"]["json_schema"] = ChatGoogle("")._fix_gemini_schema(self._wrapper.kwargs)
502
@@ -460,6 +506,17 @@ class BrowserCompatibleChatWrapper(ChatOpenRouter):
506
stop=stop,
507
**kwrgs,
508
)
509
+
510
+ # Gemini: strip triple backticks and conform schema
511
+ try:
512
+ msg = resp.choices[0].message # type: ignore
513
+ if self.provider == "gemini" and isinstance(getattr(msg, "content", None), str):
514
+ cleaned = self._gemini_clean_and_conform(msg.content) # type: ignore
515
+ if cleaned:
516
+ msg.content = cleaned
517
+ except Exception:
518
+ pass
519
+
520
except Exception as e:
521
raise e
522
python/tools/browser_agent.py
+12
-8
@@ -32,6 +32,16 @@ class State:
32
33
def __del__(self):
34
self.kill_task()
35
+ files.delete_dir(self.get_user_data_dir()) # cleanup user data dir
36
+
37
+ def get_user_data_dir(self):
38
+ return str(
39
+ Path.home()
40
+ / ".config"
41
+ / "browseruse"
42
+ / "profiles"
43
+ / f"agent_{self.agent.context.id}"
44
+ )
45
46
async def _initialize(self):
47
if self.browser_session:
@@ -46,7 +56,6 @@ class State:
56
disable_security=True,
57
chromium_sandbox=False,
58
accept_downloads=True,
49
- downloads_dir=files.get_abs_path("tmp/downloads"),
59
downloads_path=files.get_abs_path("tmp/downloads"),
60
allowed_domains=["*"],
61
executable_path=pw_binary,
@@ -54,17 +63,12 @@ class State:
63
minimum_wait_page_load_time=1.0,
64
wait_for_network_idle_page_load_time=2.0,
65
maximum_wait_page_load_time=10.0,
66
+ window_size={"width": 1024, "height": 2048},
67
screen={"width": 1024, "height": 2048},
68
viewport={"width": 1024, "height": 2048},
69
args=["--headless=new"],
70
# Use a unique user data directory to avoid conflicts
61
- user_data_dir=str(
62
- Path.home()
63
- / ".config"
64
- / "browseruse"
65
- / "profiles"
66
- / f"agent_{self.agent.context.id}"
67
- ),
71
+ user_data_dir=self.get_user_data_dir(),
72
extra_http_headers=self.agent.config.browser_http_headers or {},
73
)
74
)