camel-ai 0.2.76a0__py3-none-any.whl → 0.2.76a2__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.

Files changed (34) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +8 -1
  3. camel/environments/tic_tac_toe.py +1 -1
  4. camel/memories/__init__.py +2 -1
  5. camel/memories/agent_memories.py +3 -1
  6. camel/memories/blocks/chat_history_block.py +17 -2
  7. camel/models/base_model.py +30 -0
  8. camel/societies/workforce/single_agent_worker.py +44 -38
  9. camel/societies/workforce/workforce.py +10 -1
  10. camel/storages/object_storages/google_cloud.py +1 -1
  11. camel/toolkits/__init__.py +9 -2
  12. camel/toolkits/aci_toolkit.py +45 -0
  13. camel/toolkits/context_summarizer_toolkit.py +683 -0
  14. camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +194 -34
  15. camel/toolkits/hybrid_browser_toolkit/config_loader.py +4 -0
  16. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +67 -2
  17. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +62 -45
  18. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +489 -60
  19. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +5 -2
  20. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +72 -12
  21. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +2 -14
  22. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +1 -0
  23. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +228 -62
  24. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +4 -4
  25. camel/toolkits/markitdown_toolkit.py +27 -1
  26. camel/toolkits/note_taking_toolkit.py +18 -8
  27. camel/toolkits/slack_toolkit.py +50 -1
  28. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  29. camel/toolkits/wechat_official_toolkit.py +483 -0
  30. camel/utils/context_utils.py +395 -0
  31. {camel_ai-0.2.76a0.dist-info → camel_ai-0.2.76a2.dist-info}/METADATA +84 -6
  32. {camel_ai-0.2.76a0.dist-info → camel_ai-0.2.76a2.dist-info}/RECORD +34 -30
  33. {camel_ai-0.2.76a0.dist-info → camel_ai-0.2.76a2.dist-info}/WHEEL +0 -0
  34. {camel_ai-0.2.76a0.dist-info → camel_ai-0.2.76a2.dist-info}/licenses/LICENSE +0 -0
@@ -45,23 +45,19 @@ def action_logger(func):
45
45
  action_name = func.__name__
46
46
  start_time = time.time()
47
47
 
48
- # Log inputs (skip self)
49
48
  inputs = {
50
49
  "args": args,
51
50
  "kwargs": kwargs,
52
51
  }
53
52
 
54
53
  try:
55
- # Execute the original function
56
54
  result = await func(self, *args, **kwargs)
57
55
  execution_time = time.time() - start_time
58
56
 
59
- # Extract page load time if available
60
57
  page_load_time = None
61
58
  if isinstance(result, dict) and 'page_load_time_ms' in result:
62
59
  page_load_time = result['page_load_time_ms'] / 1000.0
63
60
 
64
- # Log success
65
61
  await self._log_action(
66
62
  action_name=action_name,
67
63
  inputs=inputs,
@@ -76,7 +72,6 @@ def action_logger(func):
76
72
  execution_time = time.time() - start_time
77
73
  error_msg = f"{type(e).__name__}: {e!s}"
78
74
 
79
- # Log error
80
75
  await self._log_action(
81
76
  action_name=action_name,
82
77
  inputs=inputs,
@@ -111,14 +106,12 @@ class WebSocketBrowserWrapper:
111
106
  self.process: Optional[subprocess.Popen] = None
112
107
  self.websocket = None
113
108
  self.server_port = None
114
- self._send_lock = asyncio.Lock() # Lock for sending messages
115
- self._receive_task = None # Background task for receiving messages
116
- self._pending_responses: Dict[
117
- str, asyncio.Future[Dict[str, Any]]
118
- ] = {} # Message ID -> Future
119
- self._server_ready_future = None # Future to track server ready state
120
-
121
- # Logging configuration
109
+ self._send_lock = asyncio.Lock()
110
+ self._receive_task = None
111
+ self._pending_responses: Dict[str, asyncio.Future[Dict[str, Any]]] = {}
112
+ self._browser_opened = False
113
+ self._server_ready_future = None
114
+
122
115
  self.browser_log_to_file = (config or {}).get(
123
116
  'browser_log_to_file', False
124
117
  )
@@ -127,10 +120,9 @@ class WebSocketBrowserWrapper:
127
120
  self.log_file_path: Optional[str] = None
128
121
  self.log_buffer: List[Dict[str, Any]] = []
129
122
  self.ts_log_file_path: Optional[str] = None
130
- self.ts_log_file = None # File handle for TypeScript logs
131
- self._log_reader_task = None # Task for reading and logging stdout
123
+ self.ts_log_file = None
124
+ self._log_reader_task = None
132
125
 
133
- # Set up log files if needed
134
126
  if self.browser_log_to_file:
135
127
  log_dir = self.log_dir if self.log_dir else "browser_log"
136
128
  os.makedirs(log_dir, exist_ok=True)
@@ -139,7 +131,6 @@ class WebSocketBrowserWrapper:
139
131
  log_dir,
140
132
  f"hybrid_browser_toolkit_ws_{timestamp}_{self.session_id}.log",
141
133
  )
142
- # Add TypeScript console log file
143
134
  self.ts_log_file_path = os.path.join(
144
135
  log_dir,
145
136
  f"typescript_console_{timestamp}_{self.session_id}.log",
@@ -154,13 +145,61 @@ class WebSocketBrowserWrapper:
154
145
  """Async context manager exit."""
155
146
  await self.stop()
156
147
 
148
+ async def _cleanup_existing_processes(self):
149
+ """Clean up any existing Node.js WebSocket server processes."""
150
+ import psutil
151
+
152
+ cleaned_count = 0
153
+ for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
154
+ try:
155
+ if (
156
+ proc.info['name']
157
+ and 'node' in proc.info['name'].lower()
158
+ and proc.info['cmdline']
159
+ and any(
160
+ 'websocket-server.js' in arg
161
+ for arg in proc.info['cmdline']
162
+ )
163
+ ):
164
+ if any(self.ts_dir in arg for arg in proc.info['cmdline']):
165
+ logger.warning(
166
+ f"Found existing WebSocket server process "
167
+ f"(PID: {proc.info['pid']}). "
168
+ f"Terminating it to prevent conflicts."
169
+ )
170
+ proc.terminate()
171
+ try:
172
+ proc.wait(timeout=3)
173
+ except psutil.TimeoutExpired:
174
+ proc.kill()
175
+ cleaned_count += 1
176
+ except (
177
+ psutil.NoSuchProcess,
178
+ psutil.AccessDenied,
179
+ psutil.ZombieProcess,
180
+ ):
181
+ pass
182
+
183
+ if cleaned_count > 0:
184
+ logger.warning(
185
+ f"Cleaned up {cleaned_count} existing WebSocket server "
186
+ f"process(es). This may have been caused by improper "
187
+ f"shutdown in previous sessions."
188
+ )
189
+ await asyncio.sleep(0.5)
190
+
157
191
  async def start(self):
158
192
  """Start the WebSocket server and connect to it."""
159
- # Check if npm is installed
193
+ await self._cleanup_existing_processes()
194
+
195
+ import platform
196
+
197
+ use_shell = platform.system() == 'Windows'
160
198
  npm_check = subprocess.run(
161
199
  ['npm', '--version'],
162
200
  capture_output=True,
163
201
  text=True,
202
+ shell=use_shell,
164
203
  )
165
204
  if npm_check.returncode != 0:
166
205
  raise RuntimeError(
@@ -169,11 +208,11 @@ class WebSocketBrowserWrapper:
169
208
  "to use the hybrid browser toolkit."
170
209
  )
171
210
 
172
- # Check if node is installed
173
211
  node_check = subprocess.run(
174
212
  ['node', '--version'],
175
213
  capture_output=True,
176
214
  text=True,
215
+ shell=use_shell,
177
216
  )
178
217
  if node_check.returncode != 0:
179
218
  raise RuntimeError(
@@ -182,7 +221,6 @@ class WebSocketBrowserWrapper:
182
221
  "to use the hybrid browser toolkit."
183
222
  )
184
223
 
185
- # Check if node_modules exists (dependencies installed)
186
224
  node_modules_path = os.path.join(self.ts_dir, 'node_modules')
187
225
  if not os.path.exists(node_modules_path):
188
226
  logger.warning("Node modules not found. Running npm install...")
@@ -191,6 +229,7 @@ class WebSocketBrowserWrapper:
191
229
  cwd=self.ts_dir,
192
230
  capture_output=True,
193
231
  text=True,
232
+ shell=use_shell,
194
233
  )
195
234
  if install_result.returncode != 0:
196
235
  logger.error(f"npm install failed: {install_result.stderr}")
@@ -200,12 +239,12 @@ class WebSocketBrowserWrapper:
200
239
  )
201
240
  logger.info("npm dependencies installed successfully")
202
241
 
203
- # Ensure the TypeScript code is built
204
242
  build_result = subprocess.run(
205
243
  ['npm', 'run', 'build'],
206
244
  cwd=self.ts_dir,
207
245
  capture_output=True,
208
246
  text=True,
247
+ shell=use_shell,
209
248
  )
210
249
  if build_result.returncode != 0:
211
250
  logger.error(f"TypeScript build failed: {build_result.stderr}")
@@ -213,20 +252,20 @@ class WebSocketBrowserWrapper:
213
252
  f"TypeScript build failed: {build_result.stderr}"
214
253
  )
215
254
 
216
- # Start the WebSocket server
255
+ # use_shell already defined above
217
256
  self.process = subprocess.Popen(
218
257
  ['node', 'websocket-server.js'],
219
258
  cwd=self.ts_dir,
220
259
  stdout=subprocess.PIPE,
221
- stderr=subprocess.STDOUT, # Redirect stderr to stdout
260
+ stderr=subprocess.STDOUT,
222
261
  text=True,
223
- bufsize=1, # Line buffered
262
+ encoding='utf-8',
263
+ bufsize=1,
264
+ shell=use_shell,
224
265
  )
225
266
 
226
- # Create a future to wait for server ready (before starting log reader)
227
267
  self._server_ready_future = asyncio.get_running_loop().create_future()
228
268
 
229
- # Start log reader task immediately after process starts
230
269
  self._log_reader_task = asyncio.create_task(
231
270
  self._read_and_log_output()
232
271
  )
@@ -237,11 +276,9 @@ class WebSocketBrowserWrapper:
237
276
  f"{self.ts_log_file_path}"
238
277
  )
239
278
 
240
- # Wait for server to output the port
241
279
  server_ready = False
242
- timeout = 10 # 10 seconds timeout
280
+ timeout = 10
243
281
 
244
- # Wait for the server to be ready
245
282
  try:
246
283
  await asyncio.wait_for(self._server_ready_future, timeout=timeout)
247
284
  server_ready = True
@@ -252,14 +289,11 @@ class WebSocketBrowserWrapper:
252
289
  with contextlib.suppress(ProcessLookupError, Exception):
253
290
  self.process.kill()
254
291
  with contextlib.suppress(Exception):
255
- # Ensure the process fully exits
256
292
  self.process.wait(timeout=2)
257
- # Cancel and await the log reader task
258
293
  if self._log_reader_task and not self._log_reader_task.done():
259
294
  self._log_reader_task.cancel()
260
295
  with contextlib.suppress(asyncio.CancelledError):
261
296
  await self._log_reader_task
262
- # Close TS log file if open
263
297
  if getattr(self, 'ts_log_file', None):
264
298
  with contextlib.suppress(Exception):
265
299
  self.ts_log_file.close()
@@ -270,7 +304,7 @@ class WebSocketBrowserWrapper:
270
304
  import psutil
271
305
 
272
306
  mem = psutil.virtual_memory()
273
- if mem.available < 1024**3: # Less than 1GB available
307
+ if mem.available < 1024**3:
274
308
  error_msg = (
275
309
  f"WebSocket server failed to start"
276
310
  f"(likely due to insufficient memory). "
@@ -280,16 +314,59 @@ class WebSocketBrowserWrapper:
280
314
 
281
315
  raise RuntimeError(error_msg)
282
316
 
283
- # Connect to the WebSocket server
284
- try:
285
- self.websocket = await websockets.connect(
286
- f"ws://localhost:{self.server_port}",
287
- ping_interval=30,
288
- ping_timeout=10,
289
- max_size=50 * 1024 * 1024, # 50MB limit to match server
290
- )
291
- logger.info("Connected to WebSocket server")
292
- except Exception as e:
317
+ max_retries = 3
318
+ retry_delays = [1, 2, 4]
319
+
320
+ for attempt in range(max_retries):
321
+ try:
322
+ connect_timeout = 10.0 + (attempt * 5.0)
323
+
324
+ logger.info(
325
+ f"Attempting to connect to WebSocket server "
326
+ f"(attempt {attempt + 1}/{max_retries}, "
327
+ f"timeout: {connect_timeout}s)"
328
+ )
329
+
330
+ self.websocket = await asyncio.wait_for(
331
+ websockets.connect(
332
+ f"ws://localhost:{self.server_port}",
333
+ ping_interval=30,
334
+ ping_timeout=10,
335
+ max_size=50 * 1024 * 1024,
336
+ ),
337
+ timeout=connect_timeout,
338
+ )
339
+ logger.info("Connected to WebSocket server")
340
+ break
341
+
342
+ except asyncio.TimeoutError:
343
+ if attempt < max_retries - 1:
344
+ delay = retry_delays[attempt]
345
+ logger.warning(
346
+ f"WebSocket handshake timeout "
347
+ f"(attempt {attempt + 1}/{max_retries}). "
348
+ f"Retrying in {delay} seconds..."
349
+ )
350
+ await asyncio.sleep(delay)
351
+ else:
352
+ raise RuntimeError(
353
+ f"Failed to connect to WebSocket server after "
354
+ f"{max_retries} attempts: Handshake timeout"
355
+ )
356
+
357
+ except Exception as e:
358
+ if attempt < max_retries - 1 and "timed out" in str(e).lower():
359
+ delay = retry_delays[attempt]
360
+ logger.warning(
361
+ f"WebSocket connection failed "
362
+ f"(attempt {attempt + 1}/{max_retries}): {e}. "
363
+ f"Retrying in {delay} seconds..."
364
+ )
365
+ await asyncio.sleep(delay)
366
+ else:
367
+ break
368
+
369
+ if not self.websocket:
293
370
  with contextlib.suppress(ProcessLookupError, Exception):
294
371
  self.process.kill()
295
372
  with contextlib.suppress(Exception):
@@ -304,45 +381,45 @@ class WebSocketBrowserWrapper:
304
381
  self.ts_log_file = None
305
382
  self.process = None
306
383
 
307
- error_msg = f"Failed to connect to WebSocket server: {e}"
308
384
  import psutil
309
385
 
310
386
  mem = psutil.virtual_memory()
311
- if mem.available < 1024**3: # Less than 1GB available
387
+
388
+ error_msg = (
389
+ "Failed to connect to WebSocket server after multiple attempts"
390
+ )
391
+ if mem.available < 1024**3:
312
392
  error_msg = (
313
- f"Failed to connect to WebSocket server"
393
+ f"Failed to connect to WebSocket server "
314
394
  f"(likely due to insufficient memory). "
315
- f"Available memory: {mem.available / 1024**3:.2f}GB"
316
- f"({mem.percent}% used). "
317
- f"Original error: {e}"
395
+ f"Available memory: {mem.available / 1024**3:.2f}GB "
396
+ f"({mem.percent}% used)"
318
397
  )
319
398
 
320
- raise RuntimeError(error_msg) from e
399
+ raise RuntimeError(error_msg)
321
400
 
322
- # Start the background receiver task
323
401
  self._receive_task = asyncio.create_task(self._receive_loop())
324
402
 
325
- # Initialize the browser toolkit
326
403
  await self._send_command('init', self.config)
327
404
 
405
+ if self.config.get('cdpUrl'):
406
+ self._browser_opened = True
407
+
328
408
  async def stop(self):
329
409
  """Stop the WebSocket connection and server."""
330
- # First, send shutdown command while receive task is still running
331
410
  if self.websocket:
332
411
  with contextlib.suppress(asyncio.TimeoutError, Exception):
333
- # Send shutdown command with a short timeout
334
412
  await asyncio.wait_for(
335
413
  self._send_command('shutdown', {}),
336
- timeout=2.0, # 2 second timeout for shutdown
414
+ timeout=2.0,
337
415
  )
338
- # Note: TimeoutError is expected as server may close
339
- # before responding
340
416
 
341
- # Close websocket connection
342
417
  with contextlib.suppress(Exception):
343
418
  await self.websocket.close()
344
419
  self.websocket = None
345
420
 
421
+ self._browser_opened = False
422
+
346
423
  # Gracefully stop the Node process before cancelling the log reader
347
424
  if self.process:
348
425
  try:
@@ -381,6 +458,54 @@ class WebSocketBrowserWrapper:
381
458
  # Ensure process handle cleared
382
459
  self.process = None
383
460
 
461
+ async def disconnect_only(self):
462
+ """Disconnect WebSocket and stop server without closing the browser.
463
+
464
+ This is useful for CDP mode where the browser should remain open.
465
+ """
466
+ if self.websocket:
467
+ with contextlib.suppress(Exception):
468
+ await self.websocket.close()
469
+ self.websocket = None
470
+
471
+ self._browser_opened = False
472
+
473
+ # Stop the Node process
474
+ if self.process:
475
+ try:
476
+ # Send SIGTERM to gracefully shutdown
477
+ self.process.terminate()
478
+ self.process.wait(timeout=3)
479
+ except subprocess.TimeoutExpired:
480
+ # Force kill if needed
481
+ with contextlib.suppress(ProcessLookupError, Exception):
482
+ self.process.kill()
483
+ self.process.wait()
484
+ except Exception as e:
485
+ logger.warning(f"Error terminating process: {e}")
486
+
487
+ # Cancel background tasks
488
+ tasks_to_cancel = [
489
+ ('_receive_task', self._receive_task),
490
+ ('_log_reader_task', self._log_reader_task),
491
+ ]
492
+ for _, task in tasks_to_cancel:
493
+ if task and not task.done():
494
+ task.cancel()
495
+ with contextlib.suppress(asyncio.CancelledError):
496
+ await task
497
+
498
+ # Close TS log file if open
499
+ if getattr(self, 'ts_log_file', None):
500
+ with contextlib.suppress(Exception):
501
+ self.ts_log_file.close()
502
+ self.ts_log_file = None
503
+
504
+ # Ensure process handle cleared
505
+ self.process = None
506
+
507
+ logger.info("WebSocket disconnected without closing browser")
508
+
384
509
  async def _log_action(
385
510
  self,
386
511
  action_name: str,
@@ -454,7 +579,16 @@ class WebSocketBrowserWrapper:
454
579
  except asyncio.CancelledError:
455
580
  break
456
581
  except Exception as e:
457
- logger.error(f"Error in receive loop: {e}")
582
+ # Check if it's a normal WebSocket close
583
+ if isinstance(e, websockets.exceptions.ConnectionClosed):
584
+ if e.code == 1000: # Normal closure
585
+ logger.debug(f"WebSocket closed normally: {e}")
586
+ else:
587
+ logger.warning(
588
+ f"WebSocket closed with code {e.code}: {e}"
589
+ )
590
+ else:
591
+ logger.error(f"Error in receive loop: {e}")
458
592
  # Notify all pending futures of the error
459
593
  for future in self._pending_responses.values():
460
594
  if not future.done():
@@ -471,7 +605,7 @@ class WebSocketBrowserWrapper:
471
605
  import psutil
472
606
 
473
607
  mem = psutil.virtual_memory()
474
- if mem.available < 1024**3: # Less than 1GB available
608
+ if mem.available < 1024**3:
475
609
  error_msg = (
476
610
  f"WebSocket not connected "
477
611
  f"(likely due to insufficient memory). "
@@ -494,7 +628,7 @@ class WebSocketBrowserWrapper:
494
628
  import psutil
495
629
 
496
630
  mem = psutil.virtual_memory()
497
- if mem.available < 1024**3: # Less than 1GB available
631
+ if mem.available < 1024**3:
498
632
  error_msg = (
499
633
  f"WebSocket connection lost "
500
634
  f"(likely due to insufficient memory). "
@@ -542,6 +676,16 @@ class WebSocketBrowserWrapper:
542
676
  except asyncio.TimeoutError:
543
677
  # Remove from pending if timeout
544
678
  self._pending_responses.pop(message_id, None)
679
+ # Special handling for shutdown command
680
+ if command == 'shutdown':
681
+ logger.debug(
682
+ "Shutdown command timeout is expected - "
683
+ "server may have closed before responding"
684
+ )
685
+ # Return a success response for shutdown
686
+ return {
687
+ 'message': 'Browser shutdown (no response received)'
688
+ }
545
689
  raise RuntimeError(
546
690
  f"Timeout waiting for response to command: {command}"
547
691
  )
@@ -555,6 +699,12 @@ class WebSocketBrowserWrapper:
555
699
  "close frame" in str(e)
556
700
  or "connection closed" in str(e).lower()
557
701
  ):
702
+ # Special handling for shutdown command
703
+ if command == 'shutdown':
704
+ logger.debug(
705
+ f"Connection closed during shutdown (expected): {e}"
706
+ )
707
+ return {'message': 'Browser shutdown (connection closed)'}
558
708
  logger.error(f"WebSocket connection closed unexpectedly: {e}")
559
709
  # Mark connection as closed
560
710
  self.websocket = None
@@ -575,17 +725,31 @@ class WebSocketBrowserWrapper:
575
725
  response = await self._send_command(
576
726
  'open_browser', {'startUrl': start_url}
577
727
  )
728
+ self._browser_opened = True
578
729
  return response
579
730
 
580
731
  @action_logger
581
732
  async def close_browser(self) -> str:
582
733
  """Close browser."""
583
734
  response = await self._send_command('close_browser', {})
735
+ self._browser_opened = False
584
736
  return response['message']
585
737
 
586
738
  @action_logger
587
739
  async def visit_page(self, url: str) -> Dict[str, Any]:
588
- """Visit a page."""
740
+ """Visit a page.
741
+
742
+ In non-CDP mode, automatically opens browser if not already open.
743
+ """
744
+ if not self._browser_opened:
745
+ is_cdp_mode = bool(self.config.get('cdpUrl'))
746
+
747
+ if not is_cdp_mode:
748
+ logger.info(
749
+ "Browser not open, automatically opening browser..."
750
+ )
751
+ await self.open_browser()
752
+
589
753
  response = await self._send_command('visit_page', {'url': url})
590
754
  return response
591
755
 
@@ -680,6 +844,8 @@ class WebSocketBrowserWrapper:
680
844
  async def type(self, ref: str, text: str) -> Dict[str, Any]:
681
845
  """Type text into an element."""
682
846
  response = await self._send_command('type', {'ref': ref, 'text': text})
847
+ # Log the response for debugging
848
+ logger.debug(f"Type response for ref {ref}: {response}")
683
849
  return response
684
850
 
685
851
  @action_logger
@@ -92,12 +92,12 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
92
92
  user_data_dir: Optional[str] = None,
93
93
  stealth: bool = False,
94
94
  web_agent_model: Optional[BaseModelBackend] = None,
95
- cache_dir: str = "tmp/",
95
+ cache_dir: Optional[str] = None,
96
96
  enabled_tools: Optional[List[str]] = None,
97
97
  browser_log_to_file: bool = False,
98
98
  log_dir: Optional[str] = None,
99
99
  session_id: Optional[str] = None,
100
- default_start_url: str = "https://google.com/",
100
+ default_start_url: Optional[str] = None,
101
101
  default_timeout: Optional[int] = None,
102
102
  short_timeout: Optional[int] = None,
103
103
  navigation_timeout: Optional[int] = None,
@@ -202,10 +202,10 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
202
202
  self._user_data_dir = user_data_dir
203
203
  self._stealth = stealth
204
204
  self._web_agent_model = web_agent_model
205
- self._cache_dir = cache_dir
205
+ self._cache_dir = cache_dir or "tmp/"
206
206
  self._browser_log_to_file = browser_log_to_file
207
207
  self._log_dir = log_dir
208
- self._default_start_url = default_start_url
208
+ self._default_start_url = default_start_url or "https://google.com/"
209
209
  self._session_id = session_id or "default"
210
210
  self._viewport_limit = viewport_limit
211
211
 
@@ -25,12 +25,38 @@ logger = get_logger(__name__)
25
25
 
26
26
  @MCPServer()
27
27
  class MarkItDownToolkit(BaseToolkit):
28
- r"""A class representing a toolkit for MarkItDown."""
28
+ r"""A class representing a toolkit for MarkItDown.
29
+
30
+ .. deprecated::
31
+ MarkItDownToolkit is deprecated. Use FileToolkit instead, which now
32
+ includes the same functionality through its read_file method that
33
+ supports both single files and multiple files.
34
+
35
+ Example migration:
36
+ # Old way
37
+ from camel.toolkits import MarkItDownToolkit
38
+ toolkit = MarkItDownToolkit()
39
+ content = toolkit.read_files(['file1.pdf', 'file2.docx'])
40
+
41
+ # New way
42
+ from camel.toolkits import FileToolkit
43
+ toolkit = FileToolkit()
44
+ content = toolkit.read_file(['file1.pdf', 'file2.docx'])
45
+ """
29
46
 
30
47
  def __init__(
31
48
  self,
32
49
  timeout: Optional[float] = None,
33
50
  ):
51
+ import warnings
52
+
53
+ warnings.warn(
54
+ "MarkItDownToolkit is deprecated and will be removed in a future "
55
+ "version. Please use FileToolkit instead, which now includes "
56
+ "read_file method that supports both single and multiple files.",
57
+ DeprecationWarning,
58
+ stacklevel=2,
59
+ )
34
60
  super().__init__(timeout=timeout)
35
61
 
36
62
  def read_files(self, file_paths: List[str]) -> Dict[str, str]:
@@ -138,33 +138,43 @@ class NoteTakingToolkit(BaseToolkit):
138
138
  self.registry.append(note_name)
139
139
  self._save_registry()
140
140
 
141
- def create_note(self, note_name: str, content: str) -> str:
141
+ def create_note(
142
+ self, note_name: str, content: str, overwrite: bool = False
143
+ ) -> str:
142
144
  r"""Creates a new note with a unique name.
143
145
 
144
146
  This function will create a new file for your note.
145
- You must provide a `note_name` that does not already exist. If you want
146
- to add content to an existing note, use the `append_note` function
147
- instead.
147
+ By default, you must provide a `note_name` that does not already exist.
148
+ If you want to add content to an existing note, use the `append_note`
149
+ function instead. If you want to overwrite an existing note, set
150
+ `overwrite=True`.
148
151
 
149
152
  Args:
150
153
  note_name (str): The name for your new note (without the .md
151
- extension). This name must be unique.
154
+ extension). This name must be unique unless overwrite is True.
152
155
  content (str): The initial content to write in the note.
156
+ overwrite (bool): Whether to overwrite an existing note.
157
+ Defaults to False.
153
158
 
154
159
  Returns:
155
160
  str: A message confirming the creation of the note or an error if
156
- the note name is not valid or already exists.
161
+ the note name is not valid or already exists
162
+ (when overwrite=False).
157
163
  """
158
164
  try:
159
165
  note_path = self.working_directory / f"{note_name}.md"
166
+ existed_before = note_path.exists()
160
167
 
161
- if note_path.exists():
168
+ if existed_before and not overwrite:
162
169
  return f"Error: Note '{note_name}.md' already exists."
163
170
 
164
171
  note_path.write_text(content, encoding="utf-8")
165
172
  self._register_note(note_name)
166
173
 
167
- return f"Note '{note_name}.md' successfully created."
174
+ if existed_before and overwrite:
175
+ return f"Note '{note_name}.md' successfully overwritten."
176
+ else:
177
+ return f"Note '{note_name}.md' successfully created."
168
178
  except Exception as e:
169
179
  return f"Error creating note: {e}"
170
180