camel-ai 0.2.73a2__py3-none-any.whl → 0.2.73a4__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.
- camel/__init__.py +1 -1
- camel/storages/vectordb_storages/__init__.py +1 -0
- camel/storages/vectordb_storages/surreal.py +100 -150
- camel/toolkits/excel_toolkit.py +153 -64
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +76 -27
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +215 -132
- camel/toolkits/message_integration.py +174 -47
- camel/toolkits/web_deploy_toolkit.py +207 -12
- {camel_ai-0.2.73a2.dist-info → camel_ai-0.2.73a4.dist-info}/METADATA +3 -1
- {camel_ai-0.2.73a2.dist-info → camel_ai-0.2.73a4.dist-info}/RECORD +12 -12
- {camel_ai-0.2.73a2.dist-info → camel_ai-0.2.73a4.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.73a2.dist-info → camel_ai-0.2.73a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -36,7 +36,7 @@ class ToolkitMessageIntegration:
|
|
|
36
36
|
>>> # Using default message handler with toolkit
|
|
37
37
|
>>> message_integration = ToolkitMessageIntegration()
|
|
38
38
|
>>> search_with_messaging = message_integration.
|
|
39
|
-
|
|
39
|
+
register_toolkits(
|
|
40
40
|
... SearchToolkit()
|
|
41
41
|
... )
|
|
42
42
|
|
|
@@ -44,7 +44,7 @@ class ToolkitMessageIntegration:
|
|
|
44
44
|
>>> def search_web(query: str) -> list:
|
|
45
45
|
... return ["result1", "result2"]
|
|
46
46
|
...
|
|
47
|
-
>>> enhanced_tools = message_integration.
|
|
47
|
+
>>> enhanced_tools = message_integration.register_functions
|
|
48
48
|
([search_web])
|
|
49
49
|
|
|
50
50
|
>>> # Using custom message handler with different parameters
|
|
@@ -148,7 +148,7 @@ class ToolkitMessageIntegration:
|
|
|
148
148
|
"""
|
|
149
149
|
return FunctionTool(self.send_message_to_user)
|
|
150
150
|
|
|
151
|
-
def
|
|
151
|
+
def register_toolkits(
|
|
152
152
|
self, toolkit: BaseToolkit, tool_names: Optional[List[str]] = None
|
|
153
153
|
) -> BaseToolkit:
|
|
154
154
|
r"""Add messaging capabilities to toolkit methods.
|
|
@@ -168,26 +168,72 @@ class ToolkitMessageIntegration:
|
|
|
168
168
|
Returns:
|
|
169
169
|
The toolkit with messaging capabilities added
|
|
170
170
|
"""
|
|
171
|
-
|
|
171
|
+
original_tools = toolkit.get_tools()
|
|
172
|
+
enhanced_methods = {}
|
|
173
|
+
for tool in original_tools:
|
|
174
|
+
method_name = tool.func.__name__
|
|
175
|
+
if tool_names is None or method_name in tool_names:
|
|
176
|
+
enhanced_func = self._add_messaging_to_tool(tool.func)
|
|
177
|
+
enhanced_methods[method_name] = enhanced_func
|
|
178
|
+
setattr(toolkit, method_name, enhanced_func)
|
|
179
|
+
original_get_tools_method = toolkit.get_tools
|
|
172
180
|
|
|
173
181
|
def enhanced_get_tools() -> List[FunctionTool]:
|
|
174
|
-
tools =
|
|
175
|
-
|
|
182
|
+
tools = []
|
|
183
|
+
for _, enhanced_method in enhanced_methods.items():
|
|
184
|
+
tools.append(FunctionTool(enhanced_method))
|
|
185
|
+
original_tools_list = original_get_tools_method()
|
|
186
|
+
for tool in original_tools_list:
|
|
187
|
+
if tool.func.__name__ not in enhanced_methods:
|
|
188
|
+
tools.append(tool)
|
|
176
189
|
|
|
177
|
-
|
|
178
|
-
if tool_names is None or tool.func.__name__ in tool_names:
|
|
179
|
-
enhanced_func = self._add_messaging_to_tool(tool.func)
|
|
180
|
-
enhanced_tools.append(FunctionTool(enhanced_func))
|
|
181
|
-
else:
|
|
182
|
-
enhanced_tools.append(tool)
|
|
183
|
-
|
|
184
|
-
return enhanced_tools
|
|
190
|
+
return tools
|
|
185
191
|
|
|
186
|
-
# Replace the get_tools method
|
|
187
192
|
toolkit.get_tools = enhanced_get_tools # type: ignore[method-assign]
|
|
193
|
+
|
|
194
|
+
# Also handle clone_for_new_session
|
|
195
|
+
# if it exists to ensure cloned toolkits
|
|
196
|
+
# also have message integration
|
|
197
|
+
if hasattr(toolkit, 'clone_for_new_session'):
|
|
198
|
+
original_clone_method = toolkit.clone_for_new_session
|
|
199
|
+
message_integration_instance = self
|
|
200
|
+
|
|
201
|
+
def enhanced_clone_for_new_session(new_session_id=None):
|
|
202
|
+
cloned_toolkit = original_clone_method(new_session_id)
|
|
203
|
+
return message_integration_instance.register_toolkits(
|
|
204
|
+
cloned_toolkit, tool_names
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
toolkit.clone_for_new_session = enhanced_clone_for_new_session
|
|
208
|
+
|
|
188
209
|
return toolkit
|
|
189
210
|
|
|
190
|
-
def
|
|
211
|
+
def _create_bound_method_wrapper(
|
|
212
|
+
self, enhanced_func: Callable, toolkit_instance
|
|
213
|
+
) -> Callable:
|
|
214
|
+
r"""Create a wrapper that mimics a bound method for _clone_tools.
|
|
215
|
+
|
|
216
|
+
This wrapper preserves the toolkit instance reference while maintaining
|
|
217
|
+
the enhanced messaging functionality.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
# Create a wrapper that appears as a bound method to _clone_tools
|
|
221
|
+
@wraps(enhanced_func)
|
|
222
|
+
def bound_method_wrapper(*args, **kwargs):
|
|
223
|
+
return enhanced_func(*args, **kwargs)
|
|
224
|
+
|
|
225
|
+
# Make it appear as a bound method by setting __self__
|
|
226
|
+
bound_method_wrapper.__self__ = toolkit_instance # type: ignore[attr-defined]
|
|
227
|
+
|
|
228
|
+
# Preserve other important attributes
|
|
229
|
+
if hasattr(enhanced_func, '__signature__'):
|
|
230
|
+
bound_method_wrapper.__signature__ = enhanced_func.__signature__ # type: ignore[attr-defined]
|
|
231
|
+
if hasattr(enhanced_func, '__doc__'):
|
|
232
|
+
bound_method_wrapper.__doc__ = enhanced_func.__doc__
|
|
233
|
+
|
|
234
|
+
return bound_method_wrapper
|
|
235
|
+
|
|
236
|
+
def register_functions(
|
|
191
237
|
self,
|
|
192
238
|
functions: Union[List[FunctionTool], List[Callable]],
|
|
193
239
|
function_names: Optional[List[str]] = None,
|
|
@@ -210,12 +256,12 @@ class ToolkitMessageIntegration:
|
|
|
210
256
|
Example:
|
|
211
257
|
>>> # With FunctionTools
|
|
212
258
|
>>> tools = [FunctionTool(search_func), FunctionTool(analyze_func)]
|
|
213
|
-
>>> enhanced_tools = message_integration.
|
|
259
|
+
>>> enhanced_tools = message_integration.register_functions
|
|
214
260
|
(tools)
|
|
215
261
|
|
|
216
262
|
>>> # With callable functions
|
|
217
263
|
>>> funcs = [search_web, analyze_data, generate_report]
|
|
218
|
-
>>> enhanced_tools = message_integration.
|
|
264
|
+
>>> enhanced_tools = message_integration.register_functions
|
|
219
265
|
(
|
|
220
266
|
... funcs,
|
|
221
267
|
... function_names=['search_web', 'analyze_data']
|
|
@@ -257,6 +303,9 @@ class ToolkitMessageIntegration:
|
|
|
257
303
|
# Get the original signature
|
|
258
304
|
original_sig = inspect.signature(func)
|
|
259
305
|
|
|
306
|
+
# Check if the function is async
|
|
307
|
+
is_async = inspect.iscoroutinefunction(func)
|
|
308
|
+
|
|
260
309
|
# Create new parameters for the enhanced function
|
|
261
310
|
new_params = list(original_sig.parameters.values())
|
|
262
311
|
|
|
@@ -321,45 +370,123 @@ class ToolkitMessageIntegration:
|
|
|
321
370
|
# Create the new signature
|
|
322
371
|
new_sig = original_sig.replace(parameters=new_params)
|
|
323
372
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
# Check if we should send a message
|
|
334
|
-
should_send = False
|
|
335
|
-
if self.use_custom_handler:
|
|
336
|
-
should_send = any(p is not None and p != '' for p in params)
|
|
337
|
-
else:
|
|
338
|
-
# For default handler, params = (title, description,
|
|
339
|
-
# attachment)
|
|
340
|
-
should_send = bool(params[0]) or bool(params[1])
|
|
373
|
+
if is_async:
|
|
374
|
+
|
|
375
|
+
@wraps(func)
|
|
376
|
+
async def wrapper(*args, **kwargs):
|
|
377
|
+
try:
|
|
378
|
+
params = self.extract_params_callback(kwargs)
|
|
379
|
+
except KeyError:
|
|
380
|
+
return await func(*args, **kwargs)
|
|
341
381
|
|
|
342
|
-
|
|
343
|
-
|
|
382
|
+
# Check if we should send a message
|
|
383
|
+
should_send = False
|
|
344
384
|
if self.use_custom_handler:
|
|
345
|
-
|
|
385
|
+
should_send = any(
|
|
386
|
+
p is not None and p != '' for p in params
|
|
387
|
+
)
|
|
346
388
|
else:
|
|
347
|
-
# For
|
|
348
|
-
title,
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
389
|
+
# For default handler, params
|
|
390
|
+
# (title, description, attachment)
|
|
391
|
+
should_send = bool(params[0]) or bool(params[1])
|
|
392
|
+
|
|
393
|
+
# Send message if needed (handle async properly)
|
|
394
|
+
if should_send:
|
|
395
|
+
try:
|
|
396
|
+
if self.use_custom_handler:
|
|
397
|
+
# Check if message handler is async
|
|
398
|
+
if inspect.iscoroutinefunction(
|
|
399
|
+
self.message_handler
|
|
400
|
+
):
|
|
401
|
+
await self.message_handler(*params)
|
|
402
|
+
else:
|
|
403
|
+
self.message_handler(*params)
|
|
404
|
+
else:
|
|
405
|
+
# For built-in handler, provide defaults
|
|
406
|
+
title, desc, attach = params
|
|
407
|
+
self.message_handler(
|
|
408
|
+
title or "Executing Tool",
|
|
409
|
+
desc or f"Running {func.__name__}",
|
|
410
|
+
attach or '',
|
|
411
|
+
)
|
|
412
|
+
except Exception as msg_error:
|
|
413
|
+
# Don't let message handler
|
|
414
|
+
# errors break the main function
|
|
415
|
+
logger.warning(f"Message handler error: {msg_error}")
|
|
416
|
+
|
|
417
|
+
# Execute the original function
|
|
418
|
+
# (kwargs have been modified to remove message params)
|
|
419
|
+
result = await func(*args, **kwargs)
|
|
420
|
+
|
|
421
|
+
return result
|
|
422
|
+
else:
|
|
423
|
+
|
|
424
|
+
@wraps(func)
|
|
425
|
+
def wrapper(*args, **kwargs):
|
|
426
|
+
# Extract parameters using the callback
|
|
427
|
+
# (this will modify kwargs by removing message params)
|
|
428
|
+
try:
|
|
429
|
+
params = self.extract_params_callback(kwargs)
|
|
430
|
+
except KeyError:
|
|
431
|
+
# If parameters are missing,
|
|
432
|
+
# just execute the original function
|
|
433
|
+
return func(*args, **kwargs)
|
|
434
|
+
|
|
435
|
+
# Check if we should send a message
|
|
436
|
+
should_send = False
|
|
437
|
+
if self.use_custom_handler:
|
|
438
|
+
should_send = any(
|
|
439
|
+
p is not None and p != '' for p in params
|
|
353
440
|
)
|
|
441
|
+
else:
|
|
442
|
+
should_send = bool(params[0]) or bool(params[1])
|
|
354
443
|
|
|
355
|
-
|
|
356
|
-
|
|
444
|
+
# Send message if needed
|
|
445
|
+
if should_send:
|
|
446
|
+
try:
|
|
447
|
+
if self.use_custom_handler:
|
|
448
|
+
self.message_handler(*params)
|
|
449
|
+
else:
|
|
450
|
+
# For built-in handler, provide defaults
|
|
451
|
+
title, desc, attach = params
|
|
452
|
+
self.message_handler(
|
|
453
|
+
title or "Executing Tool",
|
|
454
|
+
desc or f"Running {func.__name__}",
|
|
455
|
+
attach or '',
|
|
456
|
+
)
|
|
457
|
+
except Exception as msg_error:
|
|
458
|
+
logger.warning(f"Message handler error: {msg_error}")
|
|
459
|
+
|
|
460
|
+
result = func(*args, **kwargs)
|
|
357
461
|
|
|
358
|
-
|
|
462
|
+
return result
|
|
359
463
|
|
|
360
464
|
# Apply the new signature to the wrapper
|
|
361
465
|
wrapper.__signature__ = new_sig # type: ignore[attr-defined]
|
|
362
466
|
|
|
467
|
+
# Create a hybrid approach:
|
|
468
|
+
# store toolkit instance info but preserve calling behavior
|
|
469
|
+
# We'll use a property-like
|
|
470
|
+
# approach to make __self__ available when needed
|
|
471
|
+
if hasattr(func, '__self__'):
|
|
472
|
+
toolkit_instance = func.__self__
|
|
473
|
+
|
|
474
|
+
# Store the toolkit instance as an attribute
|
|
475
|
+
# Use setattr to avoid MyPy type checking issues
|
|
476
|
+
wrapper.__toolkit_instance__ = toolkit_instance # type: ignore[attr-defined]
|
|
477
|
+
|
|
478
|
+
# Create a dynamic __self__ property
|
|
479
|
+
# that only appears during introspection
|
|
480
|
+
# but doesn't interfere with normal function calls
|
|
481
|
+
def get_self():
|
|
482
|
+
return toolkit_instance
|
|
483
|
+
|
|
484
|
+
# Only set __self__
|
|
485
|
+
# if we're being called in an introspection context
|
|
486
|
+
# (like from _clone_tools)
|
|
487
|
+
# Use setattr to avoid MyPy type checking issues
|
|
488
|
+
wrapper.__self__ = toolkit_instance # type: ignore[attr-defined]
|
|
489
|
+
|
|
363
490
|
# Enhance the docstring
|
|
364
491
|
if func.__doc__:
|
|
365
492
|
enhanced_doc = func.__doc__.rstrip()
|
|
@@ -79,7 +79,9 @@ class WebDeployToolkit(BaseToolkit):
|
|
|
79
79
|
self.tag_text = self._sanitize_text(tag_text)
|
|
80
80
|
self.tag_url = self._validate_url(tag_url)
|
|
81
81
|
self.remote_server_ip = (
|
|
82
|
-
self.
|
|
82
|
+
self._validate_ip_or_domain(remote_server_ip)
|
|
83
|
+
if remote_server_ip
|
|
84
|
+
else None
|
|
83
85
|
)
|
|
84
86
|
self.remote_server_port = self._validate_port(remote_server_port)
|
|
85
87
|
self.server_registry_file = os.path.join(
|
|
@@ -87,24 +89,36 @@ class WebDeployToolkit(BaseToolkit):
|
|
|
87
89
|
)
|
|
88
90
|
self._load_server_registry()
|
|
89
91
|
|
|
90
|
-
def
|
|
91
|
-
"""Validate IP address format."""
|
|
92
|
+
def _validate_ip_or_domain(self, address: str) -> str:
|
|
93
|
+
r"""Validate IP address or domain name format."""
|
|
92
94
|
import ipaddress
|
|
95
|
+
import re
|
|
93
96
|
|
|
94
97
|
try:
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
# Try to validate as IP address first
|
|
99
|
+
ipaddress.ip_address(address)
|
|
100
|
+
return address
|
|
97
101
|
except ValueError:
|
|
98
|
-
|
|
102
|
+
# If not a valid IP, check if it's a valid domain name
|
|
103
|
+
domain_pattern = re.compile(
|
|
104
|
+
r'^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?'
|
|
105
|
+
r'(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$'
|
|
106
|
+
)
|
|
107
|
+
if domain_pattern.match(address) and len(address) <= 253:
|
|
108
|
+
return address
|
|
109
|
+
else:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"Invalid IP address or domain name: {address}"
|
|
112
|
+
)
|
|
99
113
|
|
|
100
114
|
def _validate_port(self, port: int) -> int:
|
|
101
|
-
"""Validate port number."""
|
|
115
|
+
r"""Validate port number."""
|
|
102
116
|
if not isinstance(port, int) or port < 1 or port > 65535:
|
|
103
117
|
raise ValueError(f"Invalid port number: {port}")
|
|
104
118
|
return port
|
|
105
119
|
|
|
106
120
|
def _sanitize_text(self, text: str) -> str:
|
|
107
|
-
"""Sanitize text to prevent XSS."""
|
|
121
|
+
r"""Sanitize text to prevent XSS."""
|
|
108
122
|
if not isinstance(text, str):
|
|
109
123
|
return ""
|
|
110
124
|
# Remove any HTML/script tags
|
|
@@ -119,7 +133,7 @@ class WebDeployToolkit(BaseToolkit):
|
|
|
119
133
|
return text[:100] # Limit length
|
|
120
134
|
|
|
121
135
|
def _validate_url(self, url: str) -> str:
|
|
122
|
-
"""Validate URL format."""
|
|
136
|
+
r"""Validate URL format."""
|
|
123
137
|
if not isinstance(url, str):
|
|
124
138
|
raise ValueError("URL must be a string")
|
|
125
139
|
# Basic URL validation
|
|
@@ -139,7 +153,7 @@ class WebDeployToolkit(BaseToolkit):
|
|
|
139
153
|
def _validate_subdirectory(
|
|
140
154
|
self, subdirectory: Optional[str]
|
|
141
155
|
) -> Optional[str]:
|
|
142
|
-
"""Validate subdirectory to prevent path traversal."""
|
|
156
|
+
r"""Validate subdirectory to prevent path traversal."""
|
|
143
157
|
if subdirectory is None:
|
|
144
158
|
return None
|
|
145
159
|
|
|
@@ -157,7 +171,7 @@ class WebDeployToolkit(BaseToolkit):
|
|
|
157
171
|
return subdirectory
|
|
158
172
|
|
|
159
173
|
def _is_port_available(self, port: int) -> bool:
|
|
160
|
-
"""Check if a port is available for binding."""
|
|
174
|
+
r"""Check if a port is available for binding."""
|
|
161
175
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
162
176
|
try:
|
|
163
177
|
sock.bind(('127.0.0.1', port))
|
|
@@ -604,7 +618,7 @@ class WebDeployToolkit(BaseToolkit):
|
|
|
604
618
|
return {
|
|
605
619
|
'success': False,
|
|
606
620
|
'error': (
|
|
607
|
-
f'Port {port} is already in use by
|
|
621
|
+
f'Port {port} is already in use by another process'
|
|
608
622
|
),
|
|
609
623
|
}
|
|
610
624
|
|
|
@@ -702,6 +716,44 @@ class WebDeployToolkit(BaseToolkit):
|
|
|
702
716
|
# Validate subdirectory
|
|
703
717
|
subdirectory = self._validate_subdirectory(subdirectory)
|
|
704
718
|
|
|
719
|
+
# Check if remote deployment is configured
|
|
720
|
+
if self.remote_server_ip:
|
|
721
|
+
return self._deploy_folder_to_remote_server(
|
|
722
|
+
folder_path,
|
|
723
|
+
subdirectory,
|
|
724
|
+
domain,
|
|
725
|
+
)
|
|
726
|
+
else:
|
|
727
|
+
return self._deploy_folder_to_local_server(
|
|
728
|
+
folder_path,
|
|
729
|
+
port,
|
|
730
|
+
domain,
|
|
731
|
+
subdirectory,
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
except Exception as e:
|
|
735
|
+
logger.error(f"Error deploying folder: {e}")
|
|
736
|
+
return {'success': False, 'error': str(e)}
|
|
737
|
+
|
|
738
|
+
def _deploy_folder_to_local_server(
|
|
739
|
+
self,
|
|
740
|
+
folder_path: str,
|
|
741
|
+
port: int,
|
|
742
|
+
domain: Optional[str],
|
|
743
|
+
subdirectory: Optional[str],
|
|
744
|
+
) -> Dict[str, Any]:
|
|
745
|
+
r"""Deploy folder to local server (original functionality).
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
folder_path (str): Path to the folder to deploy
|
|
749
|
+
port (int): Port to serve on
|
|
750
|
+
domain (Optional[str]): Custom domain
|
|
751
|
+
subdirectory (Optional[str]): Subdirectory path
|
|
752
|
+
|
|
753
|
+
Returns:
|
|
754
|
+
Dict[str, Any]: Deployment result
|
|
755
|
+
"""
|
|
756
|
+
try:
|
|
705
757
|
temp_dir = None
|
|
706
758
|
if self.add_branding_tag:
|
|
707
759
|
# Create temporary directory and copy all files
|
|
@@ -877,6 +929,149 @@ class WebDeployToolkit(BaseToolkit):
|
|
|
877
929
|
logger.error(f"Error deploying folder: {e}")
|
|
878
930
|
return {'success': False, 'error': str(e)}
|
|
879
931
|
|
|
932
|
+
def _deploy_folder_to_remote_server(
|
|
933
|
+
self,
|
|
934
|
+
folder_path: str,
|
|
935
|
+
subdirectory: Optional[str] = None,
|
|
936
|
+
domain: Optional[str] = None,
|
|
937
|
+
) -> Dict[str, Any]:
|
|
938
|
+
r"""Deploy folder to remote server via API.
|
|
939
|
+
|
|
940
|
+
Args:
|
|
941
|
+
folder_path (str): Path to the folder to deploy
|
|
942
|
+
subdirectory (Optional[str]): Subdirectory path for deployment
|
|
943
|
+
domain (Optional[str]): Custom domain
|
|
944
|
+
|
|
945
|
+
Returns:
|
|
946
|
+
Dict[str, Any]: Deployment result
|
|
947
|
+
"""
|
|
948
|
+
try:
|
|
949
|
+
import tempfile
|
|
950
|
+
import zipfile
|
|
951
|
+
|
|
952
|
+
import requests
|
|
953
|
+
|
|
954
|
+
# Validate subdirectory
|
|
955
|
+
subdirectory = self._validate_subdirectory(subdirectory)
|
|
956
|
+
|
|
957
|
+
# Create a temporary zip file of the folder
|
|
958
|
+
with tempfile.NamedTemporaryFile(
|
|
959
|
+
suffix='.zip', delete=False
|
|
960
|
+
) as temp_zip:
|
|
961
|
+
zip_path = temp_zip.name
|
|
962
|
+
|
|
963
|
+
try:
|
|
964
|
+
# Create zip archive
|
|
965
|
+
with zipfile.ZipFile(
|
|
966
|
+
zip_path, 'w', zipfile.ZIP_DEFLATED
|
|
967
|
+
) as zipf:
|
|
968
|
+
for root, _, files in os.walk(folder_path):
|
|
969
|
+
for file in files:
|
|
970
|
+
file_path = os.path.join(root, file)
|
|
971
|
+
# Calculate relative path within the archive
|
|
972
|
+
arcname = os.path.relpath(file_path, folder_path)
|
|
973
|
+
zipf.write(file_path, arcname)
|
|
974
|
+
|
|
975
|
+
# Read zip file as base64
|
|
976
|
+
with open(zip_path, 'rb') as f:
|
|
977
|
+
zip_data = base64.b64encode(f.read()).decode('utf-8')
|
|
978
|
+
|
|
979
|
+
# Prepare deployment data
|
|
980
|
+
deploy_data = {
|
|
981
|
+
"deployment_type": "folder",
|
|
982
|
+
"folder_data": zip_data,
|
|
983
|
+
"subdirectory": subdirectory,
|
|
984
|
+
"domain": domain,
|
|
985
|
+
"timestamp": time.time(),
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
# Add logo data if custom logo is specified
|
|
989
|
+
if self.logo_path and os.path.exists(self.logo_path):
|
|
990
|
+
try:
|
|
991
|
+
logo_ext = os.path.splitext(self.logo_path)[1]
|
|
992
|
+
logo_filename = f"custom_logo{logo_ext}"
|
|
993
|
+
|
|
994
|
+
with open(self.logo_path, 'rb') as logo_file:
|
|
995
|
+
logo_data = base64.b64encode(
|
|
996
|
+
logo_file.read()
|
|
997
|
+
).decode('utf-8')
|
|
998
|
+
|
|
999
|
+
deploy_data.update(
|
|
1000
|
+
{
|
|
1001
|
+
"logo_data": logo_data,
|
|
1002
|
+
"logo_ext": logo_ext,
|
|
1003
|
+
"logo_filename": logo_filename,
|
|
1004
|
+
}
|
|
1005
|
+
)
|
|
1006
|
+
except Exception as logo_error:
|
|
1007
|
+
logger.warning(
|
|
1008
|
+
f"Failed to process custom logo: {logo_error}"
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
# Send to remote server API
|
|
1012
|
+
api_url = f"http://{self.remote_server_ip}:{self.remote_server_port}/api/deploy"
|
|
1013
|
+
|
|
1014
|
+
response = requests.post(
|
|
1015
|
+
api_url,
|
|
1016
|
+
json=deploy_data,
|
|
1017
|
+
timeout=self.timeout
|
|
1018
|
+
or 60, # Extended timeout for folder uploads
|
|
1019
|
+
allow_redirects=False,
|
|
1020
|
+
headers={'Content-Type': 'application/json'},
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
if response.status_code == 200:
|
|
1024
|
+
result = response.json()
|
|
1025
|
+
|
|
1026
|
+
# Build URLs
|
|
1027
|
+
base_url = f"http://{self.remote_server_ip}:{self.remote_server_port}"
|
|
1028
|
+
deployed_url = (
|
|
1029
|
+
f"{base_url}/{subdirectory}/"
|
|
1030
|
+
if subdirectory
|
|
1031
|
+
else base_url
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
return {
|
|
1035
|
+
'success': True,
|
|
1036
|
+
'remote_url': deployed_url,
|
|
1037
|
+
'server_ip': self.remote_server_ip,
|
|
1038
|
+
'subdirectory': subdirectory,
|
|
1039
|
+
'domain': domain,
|
|
1040
|
+
'message': (
|
|
1041
|
+
f'Successfully deployed folder to remote server!\n'
|
|
1042
|
+
f' • Access URL: {deployed_url}\n'
|
|
1043
|
+
f' • Server: '
|
|
1044
|
+
f'{self.remote_server_ip}:{self.remote_server_port}'
|
|
1045
|
+
),
|
|
1046
|
+
'branding_tag_added': self.add_branding_tag,
|
|
1047
|
+
'logo_processed': result.get('logo_processed', False),
|
|
1048
|
+
}
|
|
1049
|
+
else:
|
|
1050
|
+
return {
|
|
1051
|
+
'success': False,
|
|
1052
|
+
'error': (
|
|
1053
|
+
f'Remote folder deployment failed: '
|
|
1054
|
+
f'HTTP {response.status_code}'
|
|
1055
|
+
),
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
finally:
|
|
1059
|
+
# Clean up temporary zip file
|
|
1060
|
+
if os.path.exists(zip_path):
|
|
1061
|
+
os.unlink(zip_path)
|
|
1062
|
+
|
|
1063
|
+
except ImportError:
|
|
1064
|
+
return {
|
|
1065
|
+
'success': False,
|
|
1066
|
+
'error': 'Remote deployment requires requests library. '
|
|
1067
|
+
'Install with: pip install requests',
|
|
1068
|
+
}
|
|
1069
|
+
except Exception as e:
|
|
1070
|
+
return {
|
|
1071
|
+
'success': False,
|
|
1072
|
+
'error': f'Remote folder deployment error: {e!s}',
|
|
1073
|
+
}
|
|
1074
|
+
|
|
880
1075
|
def stop_server(self, port: int) -> Dict[str, Any]:
|
|
881
1076
|
r"""Stop a running server on the specified port.
|
|
882
1077
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: camel-ai
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.73a4
|
|
4
4
|
Summary: Communicative Agents for AI Society Study
|
|
5
5
|
Project-URL: Homepage, https://www.camel-ai.org/
|
|
6
6
|
Project-URL: Repository, https://github.com/camel-ai/camel
|
|
@@ -124,6 +124,7 @@ Requires-Dist: slack-bolt<2,>=1.20.1; extra == 'all'
|
|
|
124
124
|
Requires-Dist: slack-sdk<4,>=3.27.2; extra == 'all'
|
|
125
125
|
Requires-Dist: soundfile<0.14,>=0.13; extra == 'all'
|
|
126
126
|
Requires-Dist: stripe<12,>=11.3.0; extra == 'all'
|
|
127
|
+
Requires-Dist: surrealdb>=1.0.6; extra == 'all'
|
|
127
128
|
Requires-Dist: sympy<2,>=1.13.3; extra == 'all'
|
|
128
129
|
Requires-Dist: tabulate>=0.9.0; extra == 'all'
|
|
129
130
|
Requires-Dist: tavily-python<0.6,>=0.5.0; extra == 'all'
|
|
@@ -336,6 +337,7 @@ Requires-Dist: pyobvector>=0.1.18; extra == 'storage'
|
|
|
336
337
|
Requires-Dist: pytidb-experimental==0.0.1.dev4; extra == 'storage'
|
|
337
338
|
Requires-Dist: qdrant-client<2,>=1.9.0; extra == 'storage'
|
|
338
339
|
Requires-Dist: redis<6,>=5.0.6; extra == 'storage'
|
|
340
|
+
Requires-Dist: surrealdb>=1.0.6; extra == 'storage'
|
|
339
341
|
Requires-Dist: weaviate-client>=4.15.0; extra == 'storage'
|
|
340
342
|
Provides-Extra: web-tools
|
|
341
343
|
Requires-Dist: apify-client<2,>=1.8.1; extra == 'web-tools'
|