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.
- camel/__init__.py +1 -1
- camel/agents/_types.py +6 -2
- camel/agents/chat_agent.py +297 -16
- camel/messages/base.py +2 -6
- camel/services/agent_openapi_server.py +380 -0
- camel/toolkits/__init__.py +2 -2
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/__init__.py +2 -2
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/actions.py +47 -11
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/agent.py +21 -11
- camel/toolkits/{non_visual_browser_toolkit/nv_browser_session.py → hybrid_browser_toolkit/browser_session.py} +64 -10
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +1002 -0
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/snapshot.py +16 -4
- camel/toolkits/{non_visual_browser_toolkit/snapshot.js → hybrid_browser_toolkit/unified_analyzer.js} +171 -15
- camel/types/agents/tool_calling_record.py +4 -1
- camel/types/enums.py +24 -24
- camel/utils/tool_result.py +44 -0
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a3.dist-info}/METADATA +16 -2
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a3.dist-info}/RECORD +20 -18
- camel/toolkits/non_visual_browser_toolkit/browser_non_visual_toolkit.py +0 -446
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a3.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a3.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
#
|
|
148
|
-
|
|
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:
|
|
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)
|