camel-ai 0.2.71a2__py3-none-any.whl → 0.2.71a3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of camel-ai might be problematic. Click here for more details.

@@ -57,13 +57,12 @@ class NVBrowserSession:
57
57
 
58
58
  def __new__(
59
59
  cls, *, headless: bool = True, user_data_dir: Optional[str] = None
60
- ):
61
- loop = asyncio.get_running_loop()
62
- if loop not in cls._sessions:
63
- instance = super().__new__(cls)
64
- instance._initialized = False
65
- cls._sessions[loop] = instance
66
- return cls._sessions[loop]
60
+ ) -> "NVBrowserSession":
61
+ # Defer event loop lookup until we actually need it
62
+ # This allows creation outside of async context
63
+ instance = super().__new__(cls)
64
+ instance._initialized = False
65
+ return instance
67
66
 
68
67
  def __init__(
69
68
  self, *, headless: bool = True, user_data_dir: Optional[str] = None
@@ -90,6 +89,47 @@ class NVBrowserSession:
90
89
  # Browser lifecycle helpers
91
90
  # ------------------------------------------------------------------
92
91
  async def ensure_browser(self) -> None:
92
+ r"""Ensure browser is ready, implementing singleton pattern per event
93
+ loop.
94
+ """
95
+ # Check if we need to reuse or create a session for this event loop
96
+ try:
97
+ loop = asyncio.get_running_loop()
98
+ except RuntimeError as e:
99
+ raise RuntimeError(
100
+ "ensure_browser() must be called from within an async context"
101
+ ) from e
102
+
103
+ # Check if there's already a session for this loop
104
+ if loop in self._sessions and self._sessions[loop] is not self:
105
+ # Copy the existing session's browser resources
106
+ existing = self._sessions[loop]
107
+ # Wait for existing session to be fully initialized
108
+ async with existing._ensure_lock:
109
+ if (
110
+ existing._initialized
111
+ and existing._page is not None
112
+ and existing._playwright is not None
113
+ ):
114
+ try:
115
+ # Verify the page is still responsive
116
+ await existing._page.title()
117
+ self._playwright = existing._playwright
118
+ self._browser = existing._browser
119
+ self._context = existing._context
120
+ self._page = existing._page
121
+ self.snapshot = existing.snapshot
122
+ self.executor = existing.executor
123
+ self._initialized = True
124
+ return
125
+ except Exception:
126
+ # Existing session is broken, continue with new
127
+ # initialization
128
+ pass
129
+
130
+ # Register this instance for the current loop
131
+ self._sessions[loop] = self
132
+
93
133
  # Serialise initialisation to avoid race conditions where multiple
94
134
  # concurrent coroutine calls create multiple browser instances for
95
135
  # the same NVBrowserSession.
@@ -98,6 +138,7 @@ class NVBrowserSession:
98
138
 
99
139
  # Moved original logic to helper
100
140
  async def _ensure_browser_inner(self) -> None:
141
+ r"""Internal browser initialization logic."""
101
142
  from playwright.async_api import async_playwright
102
143
 
103
144
  if self._page is not None:
@@ -144,11 +185,23 @@ class NVBrowserSession:
144
185
  r"""Close all browser resources, ensuring cleanup even if some
145
186
  operations fail.
146
187
  """
147
- # The close method will now only close the *current* event-loop's
148
- # browser instance. Use `close_all_sessions` for a full cleanup.
188
+ # Remove this session from the sessions dict and close resources
189
+ try:
190
+ loop = asyncio.get_running_loop()
191
+ if loop in self._sessions and self._sessions[loop] is self:
192
+ del self._sessions[loop]
193
+ except RuntimeError:
194
+ pass # No running loop, that's okay
195
+
196
+ # Clean up any stale loop references
197
+ stale_loops = [loop for loop in self._sessions if loop.is_closed()]
198
+ for loop in stale_loops:
199
+ del self._sessions[loop]
200
+
149
201
  await self._close_session()
150
202
 
151
203
  async def _close_session(self) -> None:
204
+ r"""Internal session cleanup with comprehensive error handling."""
152
205
  errors: list[str] = []
153
206
 
154
207
  # Close context first (which closes pages)
@@ -204,6 +257,7 @@ class NVBrowserSession:
204
257
  # Convenience wrappers around common actions
205
258
  # ------------------------------------------------------------------
206
259
  async def visit(self, url: str) -> str:
260
+ r"""Navigate to a URL with proper error handling."""
207
261
  await self.ensure_browser()
208
262
  assert self._page is not None
209
263
 
@@ -233,7 +287,7 @@ class NVBrowserSession:
233
287
  force_refresh=force_refresh, diff_only=diff_only
234
288
  )
235
289
 
236
- async def exec_action(self, action: dict[str, Any]) -> str:
290
+ async def exec_action(self, action: Dict[str, Any]) -> str:
237
291
  await self.ensure_browser()
238
292
  assert self.executor is not None
239
293
  return await self.executor.execute(action)