camel-ai 0.2.71a4__py3-none-any.whl → 0.2.71a6__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/chat_agent.py +1533 -135
- camel/agents/repo_agent.py +2 -1
- camel/benchmarks/browsecomp.py +6 -6
- camel/logger.py +1 -1
- camel/messages/base.py +12 -1
- camel/models/azure_openai_model.py +96 -7
- camel/models/base_model.py +68 -10
- camel/models/deepseek_model.py +5 -0
- camel/models/gemini_model.py +5 -0
- camel/models/litellm_model.py +48 -16
- camel/models/model_manager.py +24 -6
- camel/models/openai_compatible_model.py +109 -5
- camel/models/openai_model.py +117 -8
- camel/societies/workforce/prompts.py +68 -5
- camel/societies/workforce/role_playing_worker.py +65 -7
- camel/societies/workforce/single_agent_worker.py +72 -18
- camel/societies/workforce/structured_output_handler.py +500 -0
- camel/societies/workforce/utils.py +67 -2
- camel/societies/workforce/workforce.py +527 -114
- camel/societies/workforce/workforce_logger.py +0 -8
- camel/tasks/task.py +3 -1
- camel/toolkits/__init__.py +2 -0
- camel/toolkits/file_write_toolkit.py +526 -121
- camel/toolkits/hybrid_browser_toolkit/actions.py +235 -60
- camel/toolkits/hybrid_browser_toolkit/agent.py +25 -8
- camel/toolkits/hybrid_browser_toolkit/browser_session.py +574 -164
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +996 -126
- camel/toolkits/hybrid_browser_toolkit/stealth_config.py +116 -0
- camel/toolkits/hybrid_browser_toolkit/stealth_script.js +0 -0
- camel/toolkits/message_agent_toolkit.py +608 -0
- camel/toolkits/note_taking_toolkit.py +7 -13
- {camel_ai-0.2.71a4.dist-info → camel_ai-0.2.71a6.dist-info}/METADATA +6 -4
- {camel_ai-0.2.71a4.dist-info → camel_ai-0.2.71a6.dist-info}/RECORD +36 -32
- {camel_ai-0.2.71a4.dist-info → camel_ai-0.2.71a6.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.71a4.dist-info → camel_ai-0.2.71a6.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
import asyncio
|
|
15
|
-
from typing import TYPE_CHECKING, Any, Dict
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from playwright.async_api import Page
|
|
@@ -26,24 +26,33 @@ class ActionExecutor:
|
|
|
26
26
|
SHORT_TIMEOUT = 2000 # 2 seconds
|
|
27
27
|
MAX_SCROLL_AMOUNT = 5000 # Maximum scroll distance in pixels
|
|
28
28
|
|
|
29
|
-
def __init__(self, page: "Page"):
|
|
29
|
+
def __init__(self, page: "Page", session: Optional[Any] = None):
|
|
30
30
|
self.page = page
|
|
31
|
+
self.session = session # HybridBrowserSession instance
|
|
31
32
|
|
|
32
33
|
# ------------------------------------------------------------------
|
|
33
34
|
# Public helpers
|
|
34
35
|
# ------------------------------------------------------------------
|
|
35
|
-
async def execute(self, action: Dict[str, Any]) -> str:
|
|
36
|
-
r"""Execute an action and return
|
|
36
|
+
async def execute(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
37
|
+
r"""Execute an action and return detailed result information."""
|
|
37
38
|
if not action:
|
|
38
|
-
return
|
|
39
|
+
return {
|
|
40
|
+
"success": False,
|
|
41
|
+
"message": "No action to execute",
|
|
42
|
+
"details": {},
|
|
43
|
+
}
|
|
39
44
|
|
|
40
45
|
action_type = action.get("type")
|
|
41
46
|
if not action_type:
|
|
42
|
-
return
|
|
47
|
+
return {
|
|
48
|
+
"success": False,
|
|
49
|
+
"message": "Error: action has no type",
|
|
50
|
+
"details": {},
|
|
51
|
+
}
|
|
43
52
|
|
|
44
53
|
try:
|
|
45
54
|
# small helper to ensure basic stability
|
|
46
|
-
await self._wait_dom_stable()
|
|
55
|
+
# await self._wait_dom_stable()
|
|
47
56
|
|
|
48
57
|
handler = {
|
|
49
58
|
"click": self._click,
|
|
@@ -56,24 +65,41 @@ class ActionExecutor:
|
|
|
56
65
|
}.get(action_type)
|
|
57
66
|
|
|
58
67
|
if handler is None:
|
|
59
|
-
return
|
|
68
|
+
return {
|
|
69
|
+
"success": False,
|
|
70
|
+
"message": f"Error: Unknown action type '{action_type}'",
|
|
71
|
+
"details": {"action_type": action_type},
|
|
72
|
+
}
|
|
60
73
|
|
|
61
|
-
|
|
74
|
+
result = await handler(action)
|
|
75
|
+
return {
|
|
76
|
+
"success": True,
|
|
77
|
+
"message": result["message"],
|
|
78
|
+
"details": result.get("details", {}),
|
|
79
|
+
}
|
|
62
80
|
except Exception as exc:
|
|
63
|
-
return
|
|
81
|
+
return {
|
|
82
|
+
"success": False,
|
|
83
|
+
"message": f"Error executing {action_type}: {exc}",
|
|
84
|
+
"details": {"action_type": action_type, "error": str(exc)},
|
|
85
|
+
}
|
|
64
86
|
|
|
65
87
|
# ------------------------------------------------------------------
|
|
66
88
|
# Internal handlers
|
|
67
89
|
# ------------------------------------------------------------------
|
|
68
|
-
async def _click(self, action: Dict[str, Any]) -> str:
|
|
69
|
-
r"""Handle click actions with
|
|
90
|
+
async def _click(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
91
|
+
r"""Handle click actions with new tab support for any clickable
|
|
92
|
+
element."""
|
|
70
93
|
ref = action.get("ref")
|
|
71
94
|
text = action.get("text")
|
|
72
95
|
selector = action.get("selector")
|
|
73
96
|
if not (ref or text or selector):
|
|
74
|
-
return
|
|
97
|
+
return {
|
|
98
|
+
"message": "Error: click requires ref/text/selector",
|
|
99
|
+
"details": {"error": "missing_selector"},
|
|
100
|
+
}
|
|
75
101
|
|
|
76
|
-
# Build strategies in priority order
|
|
102
|
+
# Build strategies in priority order
|
|
77
103
|
strategies = []
|
|
78
104
|
if ref:
|
|
79
105
|
strategies.append(f"[aria-ref='{ref}']")
|
|
@@ -82,90 +108,230 @@ class ActionExecutor:
|
|
|
82
108
|
if text:
|
|
83
109
|
strategies.append(f'text="{text}"')
|
|
84
110
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
continue
|
|
111
|
+
details: Dict[str, Any] = {
|
|
112
|
+
"ref": ref,
|
|
113
|
+
"selector": selector,
|
|
114
|
+
"text": text,
|
|
115
|
+
"strategies_tried": [],
|
|
116
|
+
"successful_strategy": None,
|
|
117
|
+
"click_method": None,
|
|
118
|
+
"new_tab_created": False,
|
|
119
|
+
}
|
|
95
120
|
|
|
96
|
-
#
|
|
121
|
+
# Find the first valid selector
|
|
122
|
+
found_selector = None
|
|
97
123
|
for sel in strategies:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
124
|
+
if await self.page.locator(sel).count() > 0:
|
|
125
|
+
found_selector = sel
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
if not found_selector:
|
|
129
|
+
details['error'] = "Element not found with any strategy"
|
|
130
|
+
return {
|
|
131
|
+
"message": "Error: Click failed, element not found",
|
|
132
|
+
"details": details,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
element = self.page.locator(found_selector).first
|
|
136
|
+
details['successful_strategy'] = found_selector
|
|
137
|
+
|
|
138
|
+
# Attempt ctrl+click first (always)
|
|
139
|
+
try:
|
|
140
|
+
if self.session:
|
|
141
|
+
async with self.page.context.expect_page(
|
|
142
|
+
timeout=self.SHORT_TIMEOUT
|
|
143
|
+
) as new_page_info:
|
|
144
|
+
await element.click(modifiers=["ControlOrMeta"])
|
|
145
|
+
new_page = await new_page_info.value
|
|
146
|
+
await new_page.wait_for_load_state('domcontentloaded')
|
|
147
|
+
new_tab_index = await self.session.register_page(new_page)
|
|
148
|
+
if new_tab_index is not None:
|
|
149
|
+
await self.session.switch_to_tab(new_tab_index)
|
|
150
|
+
self.page = new_page
|
|
151
|
+
details.update(
|
|
152
|
+
{
|
|
153
|
+
"click_method": "ctrl_click_new_tab",
|
|
154
|
+
"new_tab_created": True,
|
|
155
|
+
"new_tab_index": new_tab_index,
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
return {
|
|
159
|
+
"message": f"Clicked element (ctrl click), opened in new "
|
|
160
|
+
f"tab {new_tab_index}",
|
|
161
|
+
"details": details,
|
|
162
|
+
}
|
|
163
|
+
else:
|
|
164
|
+
await element.click(modifiers=["ControlOrMeta"])
|
|
165
|
+
details["click_method"] = "ctrl_click_no_session"
|
|
166
|
+
return {
|
|
167
|
+
"message": f"Clicked element (ctrl click, no"
|
|
168
|
+
f" session): {found_selector}",
|
|
169
|
+
"details": details,
|
|
170
|
+
}
|
|
171
|
+
except asyncio.TimeoutError:
|
|
172
|
+
# No new tab was opened, click may have still worked
|
|
173
|
+
details["click_method"] = "ctrl_click_same_tab"
|
|
174
|
+
return {
|
|
175
|
+
"message": f"Clicked element (ctrl click, "
|
|
176
|
+
f"same tab): {found_selector}",
|
|
177
|
+
"details": details,
|
|
178
|
+
}
|
|
179
|
+
except Exception as e:
|
|
180
|
+
details['strategies_tried'].append(
|
|
181
|
+
{
|
|
182
|
+
'selector': found_selector,
|
|
183
|
+
'method': 'ctrl_click',
|
|
184
|
+
'error': str(e),
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
# Fall through to fallback
|
|
104
188
|
|
|
105
|
-
|
|
189
|
+
# Fallback to normal force click if ctrl+click fails
|
|
190
|
+
try:
|
|
191
|
+
await element.click(force=True, timeout=self.DEFAULT_TIMEOUT)
|
|
192
|
+
details["click_method"] = "playwright_force_click"
|
|
193
|
+
return {
|
|
194
|
+
"message": f"Fallback clicked element: {found_selector}",
|
|
195
|
+
"details": details,
|
|
196
|
+
}
|
|
197
|
+
except Exception as e:
|
|
198
|
+
details["click_method"] = "playwright_force_click_failed"
|
|
199
|
+
details["error"] = str(e)
|
|
200
|
+
return {
|
|
201
|
+
"message": f"Error: All click strategies "
|
|
202
|
+
f"failed for {found_selector}",
|
|
203
|
+
"details": details,
|
|
204
|
+
}
|
|
106
205
|
|
|
107
|
-
async def _type(self, action: Dict[str, Any]) -> str:
|
|
206
|
+
async def _type(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
108
207
|
r"""Handle typing text into input fields."""
|
|
109
208
|
ref = action.get("ref")
|
|
110
209
|
selector = action.get("selector")
|
|
111
210
|
text = action.get("text", "")
|
|
112
211
|
if not (ref or selector):
|
|
113
|
-
return
|
|
212
|
+
return {
|
|
213
|
+
"message": "Error: type requires ref/selector",
|
|
214
|
+
"details": {"error": "missing_selector"},
|
|
215
|
+
}
|
|
216
|
+
|
|
114
217
|
target = selector or f"[aria-ref='{ref}']"
|
|
218
|
+
details = {
|
|
219
|
+
"ref": ref,
|
|
220
|
+
"selector": selector,
|
|
221
|
+
"target": target,
|
|
222
|
+
"text": text,
|
|
223
|
+
"text_length": len(text),
|
|
224
|
+
}
|
|
225
|
+
|
|
115
226
|
try:
|
|
116
227
|
await self.page.fill(target, text, timeout=self.SHORT_TIMEOUT)
|
|
117
|
-
return
|
|
228
|
+
return {
|
|
229
|
+
"message": f"Typed '{text}' into {target}",
|
|
230
|
+
"details": details,
|
|
231
|
+
}
|
|
118
232
|
except Exception as exc:
|
|
119
|
-
|
|
233
|
+
details["error"] = str(exc)
|
|
234
|
+
return {"message": f"Type failed: {exc}", "details": details}
|
|
120
235
|
|
|
121
|
-
async def _select(self, action: Dict[str, Any]) -> str:
|
|
236
|
+
async def _select(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
122
237
|
r"""Handle selecting options from dropdowns."""
|
|
123
238
|
ref = action.get("ref")
|
|
124
239
|
selector = action.get("selector")
|
|
125
240
|
value = action.get("value", "")
|
|
126
241
|
if not (ref or selector):
|
|
127
|
-
return
|
|
242
|
+
return {
|
|
243
|
+
"message": "Error: select requires ref/selector",
|
|
244
|
+
"details": {"error": "missing_selector"},
|
|
245
|
+
}
|
|
246
|
+
|
|
128
247
|
target = selector or f"[aria-ref='{ref}']"
|
|
248
|
+
details = {
|
|
249
|
+
"ref": ref,
|
|
250
|
+
"selector": selector,
|
|
251
|
+
"target": target,
|
|
252
|
+
"value": value,
|
|
253
|
+
}
|
|
254
|
+
|
|
129
255
|
try:
|
|
130
256
|
await self.page.select_option(
|
|
131
257
|
target, value, timeout=self.DEFAULT_TIMEOUT
|
|
132
258
|
)
|
|
133
|
-
return
|
|
259
|
+
return {
|
|
260
|
+
"message": f"Selected '{value}' in {target}",
|
|
261
|
+
"details": details,
|
|
262
|
+
}
|
|
134
263
|
except Exception as exc:
|
|
135
|
-
|
|
264
|
+
details["error"] = str(exc)
|
|
265
|
+
return {"message": f"Select failed: {exc}", "details": details}
|
|
136
266
|
|
|
137
|
-
async def _wait(self, action: Dict[str, Any]) -> str:
|
|
267
|
+
async def _wait(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
138
268
|
r"""Handle wait actions."""
|
|
269
|
+
details: Dict[str, Any] = {
|
|
270
|
+
"wait_type": None,
|
|
271
|
+
"timeout": None,
|
|
272
|
+
"selector": None,
|
|
273
|
+
}
|
|
274
|
+
|
|
139
275
|
if "timeout" in action:
|
|
140
276
|
ms = int(action["timeout"])
|
|
277
|
+
details["wait_type"] = "timeout"
|
|
278
|
+
details["timeout"] = ms
|
|
141
279
|
await asyncio.sleep(ms / 1000)
|
|
142
|
-
return f"Waited {ms}ms"
|
|
280
|
+
return {"message": f"Waited {ms}ms", "details": details}
|
|
143
281
|
if "selector" in action:
|
|
144
282
|
sel = action["selector"]
|
|
283
|
+
details["wait_type"] = "selector"
|
|
284
|
+
details["selector"] = sel
|
|
145
285
|
await self.page.wait_for_selector(
|
|
146
286
|
sel, timeout=self.DEFAULT_TIMEOUT
|
|
147
287
|
)
|
|
148
|
-
return f"Waited for {sel}"
|
|
149
|
-
return
|
|
288
|
+
return {"message": f"Waited for {sel}", "details": details}
|
|
289
|
+
return {
|
|
290
|
+
"message": "Error: wait requires timeout/selector",
|
|
291
|
+
"details": details,
|
|
292
|
+
}
|
|
150
293
|
|
|
151
|
-
async def _extract(self, action: Dict[str, Any]) -> str:
|
|
294
|
+
async def _extract(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
152
295
|
r"""Handle text extraction from elements."""
|
|
153
296
|
ref = action.get("ref")
|
|
154
297
|
if not ref:
|
|
155
|
-
return
|
|
298
|
+
return {
|
|
299
|
+
"message": "Error: extract requires ref",
|
|
300
|
+
"details": {"error": "missing_ref"},
|
|
301
|
+
}
|
|
302
|
+
|
|
156
303
|
target = f"[aria-ref='{ref}']"
|
|
304
|
+
details = {"ref": ref, "target": target}
|
|
305
|
+
|
|
157
306
|
await self.page.wait_for_selector(target, timeout=self.DEFAULT_TIMEOUT)
|
|
158
307
|
txt = await self.page.text_content(target)
|
|
159
|
-
return f"Extracted: {txt[:100] if txt else 'None'}"
|
|
160
308
|
|
|
161
|
-
|
|
309
|
+
details["extracted_text"] = txt
|
|
310
|
+
details["text_length"] = len(txt) if txt else 0
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
"message": f"Extracted: {txt[:100] if txt else 'None'}",
|
|
314
|
+
"details": details,
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async def _scroll(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
162
318
|
r"""Handle page scrolling with safe parameter validation."""
|
|
163
319
|
direction = action.get("direction", "down")
|
|
164
320
|
amount = action.get("amount", 300)
|
|
165
321
|
|
|
322
|
+
details = {
|
|
323
|
+
"direction": direction,
|
|
324
|
+
"requested_amount": amount,
|
|
325
|
+
"actual_amount": None,
|
|
326
|
+
"scroll_offset": None,
|
|
327
|
+
}
|
|
328
|
+
|
|
166
329
|
# Validate inputs to prevent injection
|
|
167
330
|
if direction not in ("up", "down"):
|
|
168
|
-
return
|
|
331
|
+
return {
|
|
332
|
+
"message": "Error: direction must be 'up' or 'down'",
|
|
333
|
+
"details": details,
|
|
334
|
+
}
|
|
169
335
|
|
|
170
336
|
try:
|
|
171
337
|
# Safely convert amount to integer and clamp to reasonable range
|
|
@@ -174,28 +340,37 @@ class ActionExecutor:
|
|
|
174
340
|
-self.MAX_SCROLL_AMOUNT,
|
|
175
341
|
min(self.MAX_SCROLL_AMOUNT, amount_int),
|
|
176
342
|
) # Clamp to MAX_SCROLL_AMOUNT range
|
|
343
|
+
details["actual_amount"] = amount_int
|
|
177
344
|
except (ValueError, TypeError):
|
|
178
|
-
return
|
|
345
|
+
return {
|
|
346
|
+
"message": "Error: amount must be a valid number",
|
|
347
|
+
"details": details,
|
|
348
|
+
}
|
|
179
349
|
|
|
180
350
|
# Use safe evaluation with bound parameters
|
|
181
351
|
scroll_offset = amount_int if direction == "down" else -amount_int
|
|
352
|
+
details["scroll_offset"] = scroll_offset
|
|
353
|
+
|
|
182
354
|
await self.page.evaluate(
|
|
183
355
|
"offset => window.scrollBy(0, offset)", scroll_offset
|
|
184
356
|
)
|
|
185
357
|
await asyncio.sleep(0.5)
|
|
186
|
-
return
|
|
358
|
+
return {
|
|
359
|
+
"message": f"Scrolled {direction} by {abs(amount_int)}px",
|
|
360
|
+
"details": details,
|
|
361
|
+
}
|
|
187
362
|
|
|
188
|
-
async def _enter(self, action: Dict[str, Any]) -> str:
|
|
189
|
-
r"""Handle Enter key press
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
await self.page.focus(f"[aria-ref='{ref}']")
|
|
194
|
-
elif selector:
|
|
195
|
-
await self.page.focus(selector)
|
|
363
|
+
async def _enter(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
364
|
+
r"""Handle Enter key press on the currently focused element."""
|
|
365
|
+
details = {"action_type": "enter", "target": "focused_element"}
|
|
366
|
+
|
|
367
|
+
# Press Enter on whatever element currently has focus
|
|
196
368
|
await self.page.keyboard.press("Enter")
|
|
197
369
|
await asyncio.sleep(0.3)
|
|
198
|
-
return
|
|
370
|
+
return {
|
|
371
|
+
"message": "Pressed Enter on focused element",
|
|
372
|
+
"details": details,
|
|
373
|
+
}
|
|
199
374
|
|
|
200
375
|
# utilities
|
|
201
376
|
async def _wait_dom_stable(self) -> None:
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
import json
|
|
15
15
|
import re
|
|
16
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
17
17
|
|
|
18
18
|
from camel.logger import get_logger
|
|
19
19
|
from camel.models import BaseModelBackend, ModelFactory
|
|
20
20
|
from camel.types import ModelPlatformType, ModelType
|
|
21
21
|
|
|
22
22
|
from .actions import ActionExecutor
|
|
23
|
-
from .browser_session import
|
|
23
|
+
from .browser_session import HybridBrowserSession
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from camel.agents import ChatAgent
|
|
@@ -79,10 +79,11 @@ what was accomplished
|
|
|
79
79
|
*,
|
|
80
80
|
user_data_dir: Optional[str] = None,
|
|
81
81
|
headless: bool = False,
|
|
82
|
+
stealth: bool = False,
|
|
82
83
|
model_backend: Optional[BaseModelBackend] = None,
|
|
83
84
|
):
|
|
84
|
-
self._session =
|
|
85
|
-
headless=headless, user_data_dir=user_data_dir
|
|
85
|
+
self._session = HybridBrowserSession(
|
|
86
|
+
headless=headless, user_data_dir=user_data_dir, stealth=stealth
|
|
86
87
|
)
|
|
87
88
|
from camel.agents import ChatAgent
|
|
88
89
|
|
|
@@ -101,7 +102,7 @@ what was accomplished
|
|
|
101
102
|
async def navigate(self, url: str) -> str:
|
|
102
103
|
r"""Navigate to a URL and return the snapshot."""
|
|
103
104
|
try:
|
|
104
|
-
#
|
|
105
|
+
# HybridBrowserSession handles waits internally
|
|
105
106
|
logger.debug("Navigated to URL: %s", url)
|
|
106
107
|
await self._session.visit(url)
|
|
107
108
|
return await self._session.get_snapshot(force_refresh=True)
|
|
@@ -240,11 +241,25 @@ what was accomplished
|
|
|
240
241
|
result = await self._run_action(action)
|
|
241
242
|
logger.debug("Executed action: %s | Result: %s", action, result)
|
|
242
243
|
|
|
244
|
+
success = False
|
|
245
|
+
result_for_history = ""
|
|
246
|
+
|
|
247
|
+
if isinstance(result, str):
|
|
248
|
+
success = "Error" not in result
|
|
249
|
+
result_for_history = result
|
|
250
|
+
elif isinstance(result, dict):
|
|
251
|
+
success = result.get('success', False)
|
|
252
|
+
result_for_history = result.get('message', str(result))
|
|
253
|
+
else:
|
|
254
|
+
# Fallback case
|
|
255
|
+
success = False
|
|
256
|
+
result_for_history = str(result)
|
|
257
|
+
|
|
243
258
|
self.action_history.append(
|
|
244
259
|
{
|
|
245
260
|
"action": action,
|
|
246
|
-
"result":
|
|
247
|
-
"success":
|
|
261
|
+
"result": result_for_history,
|
|
262
|
+
"success": success,
|
|
248
263
|
}
|
|
249
264
|
)
|
|
250
265
|
|
|
@@ -277,7 +292,9 @@ what was accomplished
|
|
|
277
292
|
|
|
278
293
|
logger.info("Process completed with %d steps", steps)
|
|
279
294
|
|
|
280
|
-
async def _run_action(
|
|
295
|
+
async def _run_action(
|
|
296
|
+
self, action: Dict[str, Any]
|
|
297
|
+
) -> Union[str, Dict[str, Any]]:
|
|
281
298
|
r"""Execute a single action and return the result."""
|
|
282
299
|
if action.get("type") == "navigate":
|
|
283
300
|
return await self.navigate(action.get("url", ""))
|