camel-ai 0.2.76a4__py3-none-any.whl → 0.2.76a6__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 (38) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +276 -21
  3. camel/configs/__init__.py +3 -0
  4. camel/configs/cometapi_config.py +104 -0
  5. camel/interpreters/docker/Dockerfile +3 -12
  6. camel/memories/blocks/chat_history_block.py +4 -1
  7. camel/memories/records.py +52 -8
  8. camel/messages/base.py +1 -1
  9. camel/models/__init__.py +2 -0
  10. camel/models/azure_openai_model.py +0 -6
  11. camel/models/cometapi_model.py +83 -0
  12. camel/models/model_factory.py +2 -0
  13. camel/models/openai_compatible_model.py +0 -6
  14. camel/models/zhipuai_model.py +61 -2
  15. camel/retrievers/auto_retriever.py +1 -0
  16. camel/societies/workforce/workforce.py +9 -7
  17. camel/storages/key_value_storages/json.py +15 -2
  18. camel/storages/vectordb_storages/tidb.py +8 -6
  19. camel/toolkits/__init__.py +4 -0
  20. camel/toolkits/dingtalk.py +1135 -0
  21. camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
  22. camel/toolkits/google_drive_mcp_toolkit.py +12 -31
  23. camel/toolkits/message_integration.py +3 -0
  24. camel/toolkits/notion_mcp_toolkit.py +16 -26
  25. camel/toolkits/origene_mcp_toolkit.py +8 -49
  26. camel/toolkits/playwright_mcp_toolkit.py +12 -31
  27. camel/toolkits/resend_toolkit.py +168 -0
  28. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  29. camel/toolkits/terminal_toolkit/terminal_toolkit.py +924 -0
  30. camel/toolkits/terminal_toolkit/utils.py +580 -0
  31. camel/types/enums.py +109 -0
  32. camel/types/unified_model_type.py +5 -0
  33. camel/utils/commons.py +2 -0
  34. {camel_ai-0.2.76a4.dist-info → camel_ai-0.2.76a6.dist-info}/METADATA +25 -6
  35. {camel_ai-0.2.76a4.dist-info → camel_ai-0.2.76a6.dist-info}/RECORD +37 -31
  36. camel/toolkits/terminal_toolkit.py +0 -1798
  37. {camel_ai-0.2.76a4.dist-info → camel_ai-0.2.76a6.dist-info}/WHEEL +0 -0
  38. {camel_ai-0.2.76a4.dist-info → camel_ai-0.2.76a6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,580 @@
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+
15
+ import os
16
+ import platform
17
+ import re
18
+ import shlex
19
+ import shutil
20
+ import subprocess
21
+ import sys
22
+ import venv
23
+ from typing import Optional, Set, Tuple
24
+
25
+ from camel.logger import get_logger
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ def contains_command_chaining(command: str) -> bool:
31
+ r"""Check if command contains chaining operators that could be used to
32
+ bypass security.
33
+ """
34
+ # Pattern to match command chaining operators: ;, &&, ||, |
35
+ # But exclude cases where they are inside quotes or escaped
36
+ chaining_pattern = r'''
37
+ (?<!\\) # Not preceded by backslash (not escaped)
38
+ (?: # Group for alternation
39
+ ; # Semicolon
40
+ | # OR
41
+ \|\| # Logical OR
42
+ | # OR
43
+ && # Logical AND
44
+ | # OR
45
+ (?<!\|) # Not preceded by pipe (to avoid matching ||)
46
+ \| # Single pipe
47
+ (?!\|) # Not followed by pipe (to avoid matching ||)
48
+ )
49
+ (?= # Positive lookahead
50
+ (?: # Group
51
+ [^"'] # Not a quote
52
+ | # OR
53
+ "[^"]*" # Content in double quotes
54
+ | # OR
55
+ '[^']*' # Content in single quotes
56
+ )* # Zero or more times
57
+ $ # End of string
58
+ )
59
+ '''
60
+
61
+ return bool(re.search(chaining_pattern, command, re.VERBOSE))
62
+
63
+
64
+ def sanitize_command(
65
+ command: str,
66
+ use_docker_backend: bool = False,
67
+ safe_mode: bool = True,
68
+ working_dir: Optional[str] = None,
69
+ allowed_commands: Optional[Set[str]] = None,
70
+ ) -> Tuple[bool, str]:
71
+ r"""A comprehensive command sanitizer for both local and Docker backends.
72
+
73
+ Args:
74
+ command (str): The command to sanitize
75
+ use_docker_backend (bool): Whether using Docker backend
76
+ safe_mode (bool): Whether to apply security checks
77
+ working_dir (Optional[str]): Working directory for path validation
78
+ allowed_commands (Optional[Set[str]]): Set of allowed commands
79
+
80
+ Returns:
81
+ Tuple[bool, str]: (is_safe, message_or_command)
82
+ """
83
+ # Apply security checks to both backends - security should be consistent
84
+ if not safe_mode:
85
+ return True, command # Skip all checks if safe_mode is disabled
86
+
87
+ # First check for command chaining and pipes
88
+ if contains_command_chaining(command):
89
+ return (
90
+ False,
91
+ "Command chaining (;, &&, ||, |) is not allowed "
92
+ "for security reasons.",
93
+ )
94
+
95
+ parts = shlex.split(command)
96
+ if not parts:
97
+ return False, "Empty command is not allowed."
98
+ base_cmd = parts[0].lower()
99
+
100
+ # If whitelist is defined, only allow whitelisted commands
101
+ if allowed_commands is not None:
102
+ if base_cmd not in allowed_commands:
103
+ return (
104
+ False,
105
+ f"Command '{base_cmd}' is not in the allowed commands list.",
106
+ )
107
+ # If command is whitelisted, skip the dangerous commands check
108
+ # but still apply other safety checks
109
+ else:
110
+ # Block dangerous commands (only when no whitelist is defined)
111
+ dangerous_commands = [
112
+ # System administration
113
+ 'sudo',
114
+ 'su',
115
+ 'reboot',
116
+ 'shutdown',
117
+ 'halt',
118
+ 'poweroff',
119
+ 'init',
120
+ # File system manipulation
121
+ 'rm',
122
+ 'mv',
123
+ 'chmod',
124
+ 'chown',
125
+ 'chgrp',
126
+ 'umount',
127
+ 'mount',
128
+ # Disk operations
129
+ 'dd',
130
+ 'mkfs',
131
+ 'fdisk',
132
+ 'parted',
133
+ 'fsck',
134
+ 'mkswap',
135
+ 'swapon',
136
+ 'swapoff',
137
+ # Process management
138
+ 'kill',
139
+ 'killall',
140
+ 'pkill',
141
+ 'service',
142
+ 'systemctl',
143
+ 'systemd',
144
+ # Network configuration
145
+ 'iptables',
146
+ 'ip6tables',
147
+ 'ifconfig',
148
+ 'route',
149
+ 'iptables-save',
150
+ # Cron and scheduling
151
+ 'crontab',
152
+ 'at',
153
+ 'batch',
154
+ # User management
155
+ 'useradd',
156
+ 'userdel',
157
+ 'usermod',
158
+ 'passwd',
159
+ 'chpasswd',
160
+ 'newgrp',
161
+ # Kernel modules
162
+ 'modprobe',
163
+ 'rmmod',
164
+ 'insmod',
165
+ 'lsmod',
166
+ # System information that could leak sensitive data
167
+ 'dmesg',
168
+ 'last',
169
+ 'lastlog',
170
+ 'who',
171
+ 'w',
172
+ ]
173
+ if base_cmd in dangerous_commands:
174
+ # Special handling for rm command - use regex for precise checking
175
+ if base_cmd == 'rm':
176
+ # Check for dangerous rm options using regex
177
+ dangerous_rm_pattern = (
178
+ r'\s-[^-\s]*[rf][^-\s]*\s|\s--force\s|'
179
+ r'\s--recursive\s|\s-rf\s|\s-fr\s'
180
+ )
181
+ if re.search(dangerous_rm_pattern, command, re.IGNORECASE):
182
+ return (
183
+ False,
184
+ f"Command '{base_cmd}' with forceful or "
185
+ f"recursive options is blocked for safety.",
186
+ )
187
+ # Also block rm without any target (could be dangerous)
188
+ if len(parts) < 2:
189
+ return (
190
+ False,
191
+ "rm command requires target "
192
+ "file/directory specification.",
193
+ )
194
+ else:
195
+ return False, f"Command '{base_cmd}' is blocked for safety."
196
+
197
+ # For local backend only: prevent changing
198
+ # directory outside the workspace
199
+ # Docker containers are already sandboxed,
200
+ # so this check is not needed there
201
+ if (
202
+ not use_docker_backend
203
+ and base_cmd == 'cd'
204
+ and len(parts) > 1
205
+ and working_dir
206
+ ):
207
+ target_dir = os.path.abspath(os.path.join(working_dir, parts[1]))
208
+ if not target_dir.startswith(working_dir):
209
+ return False, "Cannot 'cd' outside of the working directory."
210
+
211
+ return True, command
212
+
213
+
214
+ # Environment management utilities
215
+
216
+
217
+ def is_uv_environment() -> bool:
218
+ r"""Detect whether the current Python runtime is managed by uv."""
219
+ return (
220
+ "UV_CACHE_DIR" in os.environ
221
+ or "uv" in sys.executable
222
+ or shutil.which("uv") is not None
223
+ )
224
+
225
+
226
+ def ensure_uv_available(update_callback=None) -> Tuple[bool, Optional[str]]:
227
+ r"""Ensure uv is available, installing it if necessary.
228
+
229
+ Args:
230
+ update_callback: Optional callback function to receive status updates
231
+
232
+ Returns:
233
+ Tuple[bool, Optional[str]]: (success, uv_path)
234
+ """
235
+ # Check if uv is already available
236
+ existing_uv = shutil.which("uv")
237
+ if existing_uv is not None:
238
+ if update_callback:
239
+ update_callback(f"uv is already available at: {existing_uv}\n")
240
+ return True, existing_uv
241
+
242
+ try:
243
+ if update_callback:
244
+ update_callback("uv not found, installing...\n")
245
+
246
+ os_type = platform.system()
247
+
248
+ # Install uv using the official installer script
249
+ if os_type in ['darwin', 'linux'] or os_type.startswith('linux'):
250
+ # Use curl to download and execute the installer
251
+ install_cmd = "curl -LsSf https://astral.sh/uv/install.sh | sh"
252
+ result = subprocess.run(
253
+ install_cmd,
254
+ shell=True,
255
+ capture_output=True,
256
+ text=True,
257
+ timeout=60,
258
+ )
259
+
260
+ if result.returncode != 0:
261
+ if update_callback:
262
+ update_callback(f"Failed to install uv: {result.stderr}\n")
263
+ return False, None
264
+
265
+ # Check if uv was installed in the expected location
266
+ home = os.path.expanduser("~")
267
+ uv_bin_path = os.path.join(home, ".cargo", "bin")
268
+ uv_executable = os.path.join(uv_bin_path, "uv")
269
+
270
+ if os.path.exists(uv_executable):
271
+ if update_callback:
272
+ update_callback(
273
+ f"uv installed successfully at: {uv_executable}\n"
274
+ )
275
+ return True, uv_executable
276
+
277
+ elif os_type == 'Windows':
278
+ # Use PowerShell to install uv on Windows
279
+ install_cmd = (
280
+ "powershell -ExecutionPolicy Bypass -c "
281
+ "\"irm https://astral.sh/uv/install.ps1 | iex\""
282
+ )
283
+ result = subprocess.run(
284
+ install_cmd,
285
+ shell=True,
286
+ capture_output=True,
287
+ text=True,
288
+ timeout=60,
289
+ )
290
+
291
+ if result.returncode != 0:
292
+ if update_callback:
293
+ update_callback(f"Failed to install uv: {result.stderr}\n")
294
+ return False, None
295
+
296
+ # Check if uv was installed in the expected location on Windows
297
+ home = os.path.expanduser("~")
298
+ uv_bin_path = os.path.join(home, ".cargo", "bin")
299
+ uv_executable = os.path.join(uv_bin_path, "uv.exe")
300
+
301
+ if os.path.exists(uv_executable):
302
+ if update_callback:
303
+ update_callback(
304
+ f"uv installed successfully at: {uv_executable}\n"
305
+ )
306
+ return True, uv_executable
307
+
308
+ if update_callback:
309
+ update_callback("Failed to verify uv installation\n")
310
+ return False, None
311
+
312
+ except Exception as e:
313
+ if update_callback:
314
+ update_callback(f"Error installing uv: {e!s}\n")
315
+ logger.error(f"Failed to install uv: {e}")
316
+ return False, None
317
+
318
+
319
+ def setup_initial_env_with_uv(
320
+ env_path: str, uv_path: str, working_dir: str, update_callback=None
321
+ ) -> bool:
322
+ r"""Set up initial environment using uv."""
323
+ try:
324
+ # Create virtual environment with Python 3.10 using uv
325
+ subprocess.run(
326
+ [uv_path, "venv", "--python", "3.10", env_path],
327
+ check=True,
328
+ capture_output=True,
329
+ cwd=working_dir,
330
+ timeout=300,
331
+ )
332
+
333
+ # Get the python path from the new environment
334
+ if platform.system() == 'Windows':
335
+ python_path = os.path.join(env_path, "Scripts", "python.exe")
336
+ else:
337
+ python_path = os.path.join(env_path, "bin", "python")
338
+
339
+ # Install essential packages using uv
340
+ essential_packages = [
341
+ "pip",
342
+ "setuptools",
343
+ "wheel",
344
+ "pyautogui",
345
+ "plotly",
346
+ ]
347
+ subprocess.run(
348
+ [
349
+ uv_path,
350
+ "pip",
351
+ "install",
352
+ "--python",
353
+ python_path,
354
+ *essential_packages,
355
+ ],
356
+ check=True,
357
+ capture_output=True,
358
+ cwd=working_dir,
359
+ timeout=300,
360
+ )
361
+
362
+ if update_callback:
363
+ update_callback(
364
+ "[UV] Initial environment created with Python 3.10 "
365
+ "and essential packages"
366
+ )
367
+ return True
368
+
369
+ except subprocess.CalledProcessError as e:
370
+ error_msg = e.stderr.decode() if e.stderr else str(e)
371
+ if update_callback:
372
+ update_callback(f"UV setup failed: {error_msg}\n")
373
+ return False
374
+ except subprocess.TimeoutExpired:
375
+ if update_callback:
376
+ update_callback("UV setup timed out after 5 minutes\n")
377
+ return False
378
+
379
+
380
+ def setup_initial_env_with_venv(
381
+ env_path: str, working_dir: str, update_callback=None
382
+ ) -> bool:
383
+ r"""Set up initial environment using standard venv."""
384
+ try:
385
+ # Create virtual environment with system Python
386
+ venv.create(
387
+ env_path, with_pip=True, system_site_packages=False, symlinks=False
388
+ )
389
+
390
+ # Get pip path
391
+ if platform.system() == 'Windows':
392
+ pip_path = os.path.join(env_path, "Scripts", "pip.exe")
393
+ else:
394
+ pip_path = os.path.join(env_path, "bin", "pip")
395
+
396
+ # Upgrade pip and install essential packages
397
+ essential_packages = [
398
+ "pip",
399
+ "setuptools",
400
+ "wheel",
401
+ "pyautogui",
402
+ "plotly",
403
+ ]
404
+ subprocess.run(
405
+ [pip_path, "install", "--upgrade", *essential_packages],
406
+ check=True,
407
+ capture_output=True,
408
+ cwd=working_dir,
409
+ timeout=300,
410
+ )
411
+
412
+ if update_callback:
413
+ update_callback(
414
+ "Initial environment created with system Python "
415
+ "and essential packages"
416
+ )
417
+ return True
418
+
419
+ except subprocess.CalledProcessError as e:
420
+ error_msg = e.stderr.decode() if e.stderr else str(e)
421
+ if update_callback:
422
+ update_callback(f"Venv setup failed: {error_msg}\n")
423
+ return False
424
+ except subprocess.TimeoutExpired:
425
+ if update_callback:
426
+ update_callback("Venv setup timed out after 5 minutes\n")
427
+ return False
428
+
429
+
430
+ def clone_current_environment(
431
+ env_path: str, working_dir: str, update_callback=None
432
+ ) -> bool:
433
+ r"""Create a new Python virtual environment, optionally using uv."""
434
+ try:
435
+ if os.path.exists(env_path):
436
+ if update_callback:
437
+ update_callback(f"Using existing environment: {env_path}\n")
438
+ return True
439
+
440
+ if update_callback:
441
+ update_callback(
442
+ f"Creating new Python environment at: {env_path}\n"
443
+ )
444
+
445
+ # Try to use uv if available
446
+ success, uv_path = ensure_uv_available(update_callback)
447
+ if success and uv_path:
448
+ # Get current Python version
449
+ current_version = (
450
+ f"{sys.version_info.major}.{sys.version_info.minor}"
451
+ )
452
+
453
+ subprocess.run(
454
+ [uv_path, "venv", "--python", current_version, env_path],
455
+ check=True,
456
+ capture_output=True,
457
+ cwd=working_dir,
458
+ timeout=300,
459
+ )
460
+
461
+ # Get the python path from the new environment
462
+ if platform.system() == 'Windows':
463
+ python_path = os.path.join(env_path, "Scripts", "python.exe")
464
+ else:
465
+ python_path = os.path.join(env_path, "bin", "python")
466
+
467
+ # Install pip and setuptools using uv
468
+ subprocess.run(
469
+ [
470
+ uv_path,
471
+ "pip",
472
+ "install",
473
+ "--python",
474
+ python_path,
475
+ "pip",
476
+ "setuptools",
477
+ "wheel",
478
+ ],
479
+ check=True,
480
+ capture_output=True,
481
+ cwd=working_dir,
482
+ timeout=300,
483
+ )
484
+
485
+ if update_callback:
486
+ update_callback(
487
+ "[UV] Cloned Python environment created successfully!\n"
488
+ )
489
+ return True
490
+ else:
491
+ # Fallback to standard venv
492
+ if update_callback:
493
+ update_callback(
494
+ "Falling back to standard venv for cloning environment\n"
495
+ )
496
+
497
+ venv.create(env_path, with_pip=True, symlinks=False)
498
+
499
+ # Ensure pip is properly available
500
+ if platform.system() == 'Windows':
501
+ python_path = os.path.join(env_path, "Scripts", "python.exe")
502
+ else:
503
+ python_path = os.path.join(env_path, "bin", "python")
504
+
505
+ if os.path.exists(python_path):
506
+ subprocess.run(
507
+ [python_path, "-m", "pip", "install", "--upgrade", "pip"],
508
+ check=True,
509
+ capture_output=True,
510
+ cwd=working_dir,
511
+ timeout=60,
512
+ )
513
+ if update_callback:
514
+ update_callback(
515
+ "New Python environment created successfully with pip!"
516
+ )
517
+ else:
518
+ if update_callback:
519
+ update_callback(
520
+ f"Warning: Python executable not found at "
521
+ f"{python_path}"
522
+ )
523
+ return True
524
+
525
+ except subprocess.CalledProcessError as e:
526
+ error_msg = e.stderr.decode() if e.stderr else str(e)
527
+ if update_callback:
528
+ update_callback(f"Failed to create environment: {error_msg}\n")
529
+ logger.error(f"Failed to create environment: {error_msg}")
530
+ return False
531
+ except subprocess.TimeoutExpired:
532
+ if update_callback:
533
+ update_callback("Environment creation timed out\n")
534
+ return False
535
+ except Exception as e:
536
+ if update_callback:
537
+ update_callback(f"Failed to create environment: {e!s}\n")
538
+ logger.error(f"Failed to create environment: {e}")
539
+ return False
540
+
541
+
542
+ def check_nodejs_availability(update_callback=None) -> Tuple[bool, str]:
543
+ r"""Check if Node.js is available without modifying the system."""
544
+ try:
545
+ # Check if Node.js is already available in the system
546
+ node_result = subprocess.run(
547
+ ["node", "--version"],
548
+ check=False,
549
+ capture_output=True,
550
+ timeout=10,
551
+ )
552
+
553
+ npm_result = subprocess.run(
554
+ ["npm", "--version"],
555
+ check=False,
556
+ capture_output=True,
557
+ timeout=10,
558
+ )
559
+
560
+ if node_result.returncode == 0 and npm_result.returncode == 0:
561
+ node_version = node_result.stdout.decode().strip()
562
+ npm_version = npm_result.stdout.decode().strip()
563
+ info = (
564
+ f"Node.js {node_version} and npm {npm_version} are available"
565
+ )
566
+ if update_callback:
567
+ update_callback(f"{info}\n")
568
+ return True, info
569
+ else:
570
+ info = "Node.js not found. If needed, please install it manually."
571
+ if update_callback:
572
+ update_callback(f"Note: {info}\n")
573
+ return False, info
574
+
575
+ except Exception as e:
576
+ info = f"Could not check Node.js availability - {e}"
577
+ if update_callback:
578
+ update_callback(f"Note: {info}.\n")
579
+ logger.warning(f"Failed to check Node.js: {e}")
580
+ return False, info