diff --git a/backend/app/utils/agent.py b/backend/app/utils/agent.py index 00abb28bd..83e1e1a7a 100644 --- a/backend/app/utils/agent.py +++ b/backend/app/utils/agent.py @@ -323,6 +323,17 @@ class ListenChatAgent(ChatAgent): else: result = raw_result mask_flag = False + # Prepare result message with truncation + if isinstance(result, str): + result_msg = result + else: + result_str = repr(result) + MAX_RESULT_LENGTH = 500 + if len(result_str) > MAX_RESULT_LENGTH: + result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)" + else: + result_msg = result_str + asyncio.create_task( task_lock.put_queue( ActionDeactivateToolkitData( @@ -331,7 +342,7 @@ class ListenChatAgent(ChatAgent): "process_task_id": self.process_task_id, "toolkit_name": toolkit_name, "method_name": func_name, - "message": result if isinstance(result, str) else repr(result), + "message": result_msg, }, ) ) @@ -407,6 +418,17 @@ class ListenChatAgent(ChatAgent): traceroot_logger.error(f"Async tool execution failed for {func_name}: {e}") traceback.print_exc() + # Prepare result message with truncation + if isinstance(result, str): + result_msg = result + else: + result_str = repr(result) + MAX_RESULT_LENGTH = 500 + if len(result_str) > MAX_RESULT_LENGTH: + result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)" + else: + result_msg = result_str + await task_lock.put_queue( ActionDeactivateToolkitData( data={ @@ -414,7 +436,7 @@ class ListenChatAgent(ChatAgent): "process_task_id": self.process_task_id, "toolkit_name": toolkit_name, "method_name": func_name, - "message": result if isinstance(result, str) else repr(result), + "message": result_msg, }, ) ) diff --git a/backend/app/utils/listen/toolkit_listen.py b/backend/app/utils/listen/toolkit_listen.py index 77079c7c5..63de14117 100644 --- a/backend/app/utils/listen/toolkit_listen.py +++ b/backend/app/utils/listen/toolkit_listen.py @@ -1,8 +1,10 @@ import asyncio from functools import wraps -from inspect import iscoroutinefunction +from inspect import iscoroutinefunction, getmembers, ismethod, signature import json -from typing import Any, Callable +from typing import Any, Callable, Type, TypeVar +import threading +from concurrent.futures import ThreadPoolExecutor from loguru import logger from app.service.task import ( @@ -14,6 +16,39 @@ from app.utils.toolkit.abstract_toolkit import AbstractToolkit from app.service.task import process_task +def _safe_put_queue(task_lock, data): + """Safely put data to the queue, handling both sync and async contexts""" + try: + # Try to get current event loop + loop = asyncio.get_running_loop() + # We're in an async context, create a task + task = asyncio.create_task(task_lock.put_queue(data)) + if hasattr(task_lock, "add_background_task"): + task_lock.add_background_task(task) + except RuntimeError: + # No running event loop, we need to handle this differently + try: + # Create a new event loop in a separate thread to avoid conflicts + def run_in_thread(): + try: + # Create a new event loop for this thread + new_loop = asyncio.new_event_loop() + asyncio.set_event_loop(new_loop) + try: + new_loop.run_until_complete(task_lock.put_queue(data)) + logger.debug(f"[listen_toolkit] Successfully sent data to queue using new event loop") + finally: + new_loop.close() + except Exception as e: + logger.error(f"[listen_toolkit] Failed to send data in thread: {e}") + + # Run in a separate thread to avoid blocking + thread = threading.Thread(target=run_in_thread, daemon=True) + thread.start() + except Exception as e: + logger.error(f"[listen_toolkit] Failed to send data to queue: {e}") + + def listen_toolkit( wrap_method: Callable[..., Any] | None = None, inputs: Callable[..., str] | None = None, @@ -27,6 +62,11 @@ def listen_toolkit( @wraps(wrap) async def async_wrapper(*args, **kwargs): toolkit: AbstractToolkit = args[0] + # Check if api_task_id exists + if not hasattr(toolkit, 'api_task_id'): + logger.warning(f"[listen_toolkit] {toolkit.__class__.__name__} missing api_task_id, calling method directly") + return await func(*args, **kwargs) + task_lock = get_task_lock(toolkit.api_task_id) if inputs is not None: @@ -40,19 +80,24 @@ def listen_toolkit( kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items()) args_str = f"{args_str}, {kwargs_str}" if args_str else kwargs_str + # Truncate args_str if too long + MAX_ARGS_LENGTH = 500 + if len(args_str) > MAX_ARGS_LENGTH: + args_str = args_str[:MAX_ARGS_LENGTH] + f"... (truncated, total length: {len(args_str)} chars)" + toolkit_name = toolkit.toolkit_name() method_name = func.__name__.replace("_", " ") - await task_lock.put_queue( - ActionActivateToolkitData( - data={ - "agent_name": toolkit.agent_name, - "process_task_id": process_task.get(""), - "toolkit_name": toolkit_name, - "method_name": method_name, - "message": args_str, - }, - ) + activate_data = ActionActivateToolkitData( + data={ + "agent_name": toolkit.agent_name, + "process_task_id": process_task.get(""), + "toolkit_name": toolkit_name, + "method_name": method_name, + "message": args_str, + }, ) + logger.debug(f"[listen_toolkit] Sending activate data: {activate_data.model_dump()}") + await task_lock.put_queue(activate_data) error = None res = None try: @@ -70,21 +115,27 @@ def listen_toolkit( res_msg = json.dumps(res, ensure_ascii=False) except TypeError: # Handle cases where res contains non-serializable objects (like coroutines) - res_msg = str(res) + res_str = str(res) + # Truncate very long outputs to avoid flooding logs + MAX_LENGTH = 500 + if len(res_str) > MAX_LENGTH: + res_msg = res_str[:MAX_LENGTH] + f"... (truncated, total length: {len(res_str)} chars)" + else: + res_msg = res_str else: res_msg = str(error) - await task_lock.put_queue( - ActionDeactivateToolkitData( - data={ - "agent_name": toolkit.agent_name, - "process_task_id": process_task.get(""), - "toolkit_name": toolkit_name, - "method_name": method_name, - "message": res_msg, - }, - ) + deactivate_data = ActionDeactivateToolkitData( + data={ + "agent_name": toolkit.agent_name, + "process_task_id": process_task.get(""), + "toolkit_name": toolkit_name, + "method_name": method_name, + "message": res_msg, + }, ) + logger.debug(f"[listen_toolkit] Sending deactivate data: {deactivate_data.model_dump()}") + await task_lock.put_queue(deactivate_data) if error is not None: raise error return res @@ -96,6 +147,11 @@ def listen_toolkit( @wraps(wrap) def sync_wrapper(*args, **kwargs): toolkit: AbstractToolkit = args[0] + # Check if api_task_id exists + if not hasattr(toolkit, 'api_task_id'): + logger.warning(f"[listen_toolkit] {toolkit.__class__.__name__} missing api_task_id, calling method directly") + return func(*args, **kwargs) + task_lock = get_task_lock(toolkit.api_task_id) if inputs is not None: @@ -109,23 +165,24 @@ def listen_toolkit( kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items()) args_str = f"{args_str}, {kwargs_str}" if args_str else kwargs_str + # Truncate args_str if too long + MAX_ARGS_LENGTH = 500 + if len(args_str) > MAX_ARGS_LENGTH: + args_str = args_str[:MAX_ARGS_LENGTH] + f"... (truncated, total length: {len(args_str)} chars)" + toolkit_name = toolkit.toolkit_name() method_name = func.__name__.replace("_", " ") - task = asyncio.create_task( - task_lock.put_queue( - ActionActivateToolkitData( - data={ - "agent_name": toolkit.agent_name, - "process_task_id": process_task.get(""), - "toolkit_name": toolkit_name, - "method_name": method_name, - "message": args_str, - }, - ) - ) + activate_data = ActionActivateToolkitData( + data={ + "agent_name": toolkit.agent_name, + "process_task_id": process_task.get(""), + "toolkit_name": toolkit_name, + "method_name": method_name, + "message": args_str, + }, ) - if hasattr(task_lock, "add_background_task"): - task_lock.add_background_task(task) + logger.debug(f"[listen_toolkit sync] Sending activate data: {activate_data.model_dump()}") + _safe_put_queue(task_lock, activate_data) error = None res = None try: @@ -150,25 +207,26 @@ def listen_toolkit( res_msg = json.dumps(res, ensure_ascii=False) except TypeError: # Handle cases where res contains non-serializable objects (like coroutines) - res_msg = str(res) + res_str = str(res) + # Truncate very long outputs to avoid flooding logs + MAX_LENGTH = 500 + if len(res_str) > MAX_LENGTH: + res_msg = res_str[:MAX_LENGTH] + f"... (truncated, total length: {len(res_str)} chars)" + else: + res_msg = res_str else: res_msg = str(error) - task = asyncio.create_task( - task_lock.put_queue( - ActionDeactivateToolkitData( - data={ - "agent_name": toolkit.agent_name, - "process_task_id": process_task.get(""), - "toolkit_name": toolkit_name, - "method_name": method_name, - "message": res_msg, - }, - ) - ) + deactivate_data = ActionDeactivateToolkitData( + data={ + "agent_name": toolkit.agent_name, + "process_task_id": process_task.get(""), + "toolkit_name": toolkit_name, + "method_name": method_name, + "message": res_msg, + }, ) - if hasattr(task_lock, "add_background_task"): - task_lock.add_background_task(task) + _safe_put_queue(task_lock, deactivate_data) if error is not None: raise error return res @@ -176,3 +234,81 @@ def listen_toolkit( return sync_wrapper return decorator + + +T = TypeVar('T') + +# Methods that should not be wrapped by auto_listen_toolkit +# These are utility/helper methods that don't perform actual tool operations +EXCLUDED_METHODS = { + 'get_tools', # Tool enumeration + 'get_can_use_tools', # Tool filtering + 'toolkit_name', # Metadata getter + 'run_mcp_server', # MCP server initialization + 'model_dump', # Pydantic model serialization + 'model_dump_json', # Pydantic model serialization + 'dict', # Pydantic legacy dict method + 'json', # Pydantic legacy json method + 'copy', # Object copying + 'update', # Object update +} + + +def auto_listen_toolkit(base_toolkit_class: Type[T]) -> Callable[[Type[T]], Type[T]]: + """ + Class decorator that automatically wraps all public methods from the base toolkit + with the @listen_toolkit decorator. + + Excluded methods (not wrapped): + - get_tools, get_can_use_tools: Tool enumeration/filtering + - toolkit_name: Metadata getter + - run_mcp_server: MCP server initialization + - Pydantic serialization methods: model_dump, model_dump_json, dict, json + - Object utility methods: copy, update + + These methods are typically called during initialization or for metadata, + and should not trigger activate/deactivate events. + + Usage: + @auto_listen_toolkit(BaseNoteTakingToolkit) + class NoteTakingToolkit(BaseNoteTakingToolkit, AbstractToolkit): + agent_name: str = Agents.document_agent + """ + def class_decorator(cls: Type[T]) -> Type[T]: + + base_methods = {} + for name in dir(base_toolkit_class): + # Skip private methods and excluded helper methods + if not name.startswith('_') and name not in EXCLUDED_METHODS: + attr = getattr(base_toolkit_class, name) + if callable(attr): + base_methods[name] = attr + + for method_name, base_method in base_methods.items(): + if method_name in cls.__dict__: + continue + + sig = signature(base_method) + + def create_wrapper(method_name: str, base_method: Callable) -> Callable: + if iscoroutinefunction(base_method): + async def async_method_wrapper(self, *args, **kwargs): + return await getattr(super(cls, self), method_name)(*args, **kwargs) + async_method_wrapper.__name__ = method_name + async_method_wrapper.__signature__ = sig + return async_method_wrapper + else: + def sync_method_wrapper(self, *args, **kwargs): + return getattr(super(cls, self), method_name)(*args, **kwargs) + sync_method_wrapper.__name__ = method_name + sync_method_wrapper.__signature__ = sig + return sync_method_wrapper + + wrapper = create_wrapper(method_name, base_method) + decorated_method = listen_toolkit(base_method)(wrapper) + + setattr(cls, method_name, decorated_method) + + return cls + + return class_decorator diff --git a/backend/app/utils/toolkit/audio_analysis_toolkit.py b/backend/app/utils/toolkit/audio_analysis_toolkit.py index ff69d35a5..617110144 100644 --- a/backend/app/utils/toolkit/audio_analysis_toolkit.py +++ b/backend/app/utils/toolkit/audio_analysis_toolkit.py @@ -4,10 +4,11 @@ from camel.toolkits import AudioAnalysisToolkit as BaseAudioAnalysisToolkit from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseAudioAnalysisToolkit) class AudioAnalysisToolkit(BaseAudioAnalysisToolkit, AbstractToolkit): agent_name: str = Agents.multi_modal_agent @@ -23,14 +24,3 @@ class AudioAnalysisToolkit(BaseAudioAnalysisToolkit, AbstractToolkit): cache_dir = env("file_save_path", os.path.expanduser("~/.eigent/tmp/")) super().__init__(cache_dir, transcribe_model, audio_reasoning_model, timeout) self.api_task_id = api_task_id - - @listen_toolkit( - BaseAudioAnalysisToolkit.audio2text, - lambda _, audio_path, question: f"transcribe audio from {audio_path} and ask question: {question}", - ) - def ask_question_about_audio(self, audio_path: str, question: str) -> str: - return super().ask_question_about_audio(audio_path, question) - - @listen_toolkit(BaseAudioAnalysisToolkit.audio2text) - def audio2text(self, audio_path: str) -> str: - return super().audio2text(audio_path) diff --git a/backend/app/utils/toolkit/code_execution_toolkit.py b/backend/app/utils/toolkit/code_execution_toolkit.py index 2e0292a37..4ca394f5b 100644 --- a/backend/app/utils/toolkit/code_execution_toolkit.py +++ b/backend/app/utils/toolkit/code_execution_toolkit.py @@ -1,10 +1,11 @@ from typing import List, Literal from camel.toolkits import CodeExecutionToolkit as BaseCodeExecutionToolkit, FunctionTool from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseCodeExecutionToolkit) class CodeExecutionToolkit(BaseCodeExecutionToolkit, AbstractToolkit): agent_name: str = Agents.developer_agent @@ -21,18 +22,6 @@ class CodeExecutionToolkit(BaseCodeExecutionToolkit, AbstractToolkit): self.api_task_id = api_task_id super().__init__(sandbox, verbose, unsafe_mode, import_white_list, require_confirm, timeout) - @listen_toolkit( - BaseCodeExecutionToolkit.execute_code, - ) - def execute_code(self, code: str, code_type: str = "python") -> str: - return super().execute_code(code, code_type) - - @listen_toolkit( - BaseCodeExecutionToolkit.execute_command, - ) - def execute_command(self, command: str) -> str | tuple[str, str]: - return super().execute_command(command) - def get_tools(self) -> List[FunctionTool]: return [ FunctionTool(self.execute_code), diff --git a/backend/app/utils/toolkit/craw4ai_toolkit.py b/backend/app/utils/toolkit/craw4ai_toolkit.py index a422465dd..65d1b51c7 100644 --- a/backend/app/utils/toolkit/craw4ai_toolkit.py +++ b/backend/app/utils/toolkit/craw4ai_toolkit.py @@ -1,10 +1,11 @@ from camel.toolkits import Crawl4AIToolkit as BaseCrawl4AIToolkit from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseCrawl4AIToolkit) class Crawl4AIToolkit(BaseCrawl4AIToolkit, AbstractToolkit): agent_name: str = Agents.search_agent @@ -12,18 +13,5 @@ class Crawl4AIToolkit(BaseCrawl4AIToolkit, AbstractToolkit): self.api_task_id = api_task_id super().__init__(timeout) - # async def _get_client(self): - # r"""Get or create the AsyncWebCrawler client.""" - # if self._client is None: - # from crawl4ai import AsyncWebCrawler - - # self._client = AsyncWebCrawler(use_managed_browser=True) - # await self._client.__aenter__() - # return self._client - - @listen_toolkit(BaseCrawl4AIToolkit.scrape) - async def scrape(self, url: str) -> str: - return await super().scrape(url) - def toolkit_name(self) -> str: return "Crawl Toolkit" diff --git a/backend/app/utils/toolkit/excel_toolkit.py b/backend/app/utils/toolkit/excel_toolkit.py index 9e73ebad0..4a9ee0c97 100644 --- a/backend/app/utils/toolkit/excel_toolkit.py +++ b/backend/app/utils/toolkit/excel_toolkit.py @@ -3,10 +3,11 @@ from camel.toolkits import ExcelToolkit as BaseExcelToolkit from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseExcelToolkit) class ExcelToolkit(BaseExcelToolkit, AbstractToolkit): agent_name: str = Agents.document_agent @@ -20,7 +21,3 @@ class ExcelToolkit(BaseExcelToolkit, AbstractToolkit): if working_directory is None: working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) super().__init__(timeout=timeout, working_directory=working_directory) - - @listen_toolkit(BaseExcelToolkit.extract_excel_content) - def extract_excel_content(self, document_path: str) -> str: - return super().extract_excel_content(document_path) diff --git a/backend/app/utils/toolkit/file_write_toolkit.py b/backend/app/utils/toolkit/file_write_toolkit.py index 02b71d018..210ffc9c3 100644 --- a/backend/app/utils/toolkit/file_write_toolkit.py +++ b/backend/app/utils/toolkit/file_write_toolkit.py @@ -5,10 +5,11 @@ from camel.toolkits import FileToolkit as BaseFileToolkit from app.component.environment import env from app.service.task import process_task from app.service.task import ActionWriteFileData, Agents, get_task_lock -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseFileToolkit) class FileToolkit(BaseFileToolkit, AbstractToolkit): agent_name: str = Agents.document_agent @@ -54,15 +55,3 @@ class FileToolkit(BaseFileToolkit, AbstractToolkit): ) ) return res - - @listen_toolkit( - BaseFileToolkit.read_file, - ) - def read_file(self, file_paths: str | list[str]) -> str | dict[str, str]: - return super().read_file(file_paths) - - @listen_toolkit( - BaseFileToolkit.edit_file, - ) - def edit_file(self, file_path: str, old_content: str, new_content: str) -> str: - return super().edit_file(file_path, old_content, new_content) diff --git a/backend/app/utils/toolkit/github_toolkit.py b/backend/app/utils/toolkit/github_toolkit.py index 87ba2ed16..e5204e294 100644 --- a/backend/app/utils/toolkit/github_toolkit.py +++ b/backend/app/utils/toolkit/github_toolkit.py @@ -3,10 +3,11 @@ from camel.toolkits import GithubToolkit as BaseGithubToolkit from camel.toolkits.function_tool import FunctionTool from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseGithubToolkit) class GithubToolkit(BaseGithubToolkit, AbstractToolkit): agent_name: str = Agents.developer_agent @@ -19,86 +20,6 @@ class GithubToolkit(BaseGithubToolkit, AbstractToolkit): super().__init__(access_token, timeout) self.api_task_id = api_task_id - @listen_toolkit( - BaseGithubToolkit.create_pull_request, - lambda _, - repo_name, - file_path, - new_content, - pr_title, - body, - branch_name: f"Create PR in {repo_name} for {file_path} with title '{pr_title}', branch '{branch_name}', content '{new_content}'", - ) - def create_pull_request( - self, - repo_name: str, - file_path: str, - new_content: str, - pr_title: str, - body: str, - branch_name: str, - ) -> str: - return super().create_pull_request(repo_name, file_path, new_content, pr_title, body, branch_name) - - @listen_toolkit( - BaseGithubToolkit.get_issue_list, - lambda _, repo_name, state="all": f"Get issue list from {repo_name} with state '{state}'", - lambda issues: f"Retrieved {len(issues)} issues", - ) - def get_issue_list( - self, repo_name: str, state: Literal["open", "closed", "all"] = "all" - ) -> list[dict[str, object]]: - return super().get_issue_list(repo_name, state) - - @listen_toolkit( - BaseGithubToolkit.get_issue_content, - lambda _, repo_name, issue_number: f"Get content of issue {issue_number} from {repo_name}", - ) - def get_issue_content(self, repo_name: str, issue_number: int) -> str: - return super().get_issue_content(repo_name, issue_number) - - @listen_toolkit( - BaseGithubToolkit.get_pull_request_list, - lambda _, repo_name, state="all": f"Get pull request list from {repo_name} with state '{state}'", - lambda prs: f"Retrieved {len(prs)} pull requests", - ) - def get_pull_request_list( - self, repo_name: str, state: Literal["open", "closed", "all"] = "all" - ) -> list[dict[str, object]]: - return super().get_pull_request_list(repo_name, state) - - @listen_toolkit( - BaseGithubToolkit.get_pull_request_code, - lambda _, repo_name, pr_number: f"Get code for pull request {pr_number} in {repo_name}", - lambda code: f"Retrieved {len(code)} code files", - ) - def get_pull_request_code(self, repo_name: str, pr_number: int) -> list[dict[str, str]]: - return super().get_pull_request_code(repo_name, pr_number) - - @listen_toolkit( - BaseGithubToolkit.get_pull_request_comments, - lambda _, repo_name, pr_number: f"Get comments for pull request {pr_number} in {repo_name}", - lambda comments: f"Retrieved {len(comments)} comments", - ) - def get_pull_request_comments(self, repo_name: str, pr_number: int) -> list[dict[str, str]]: - return super().get_pull_request_comments(repo_name, pr_number) - - @listen_toolkit( - BaseGithubToolkit.get_all_file_paths, - lambda _, repo_name, path="": f"Get all file paths from {repo_name}, path '{path}'", - lambda paths: f"Retrieved {len(paths)} file paths", - ) - def get_all_file_paths(self, repo_name: str, path: str = "") -> list[str]: - return super().get_all_file_paths(repo_name, path) - - @listen_toolkit( - BaseGithubToolkit.retrieve_file_content, - lambda _, repo_name, file_path: f"Retrieve content of file {file_path} from {repo_name}", - lambda content: f"Retrieved content of length {len(content)}", - ) - def retrieve_file_content(self, repo_name: str, file_path: str) -> str: - return super().retrieve_file_content(repo_name, file_path) - @classmethod def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: if env("GITHUB_ACCESS_TOKEN"): diff --git a/backend/app/utils/toolkit/google_calendar_toolkit.py b/backend/app/utils/toolkit/google_calendar_toolkit.py index 34b07bd31..39509b150 100644 --- a/backend/app/utils/toolkit/google_calendar_toolkit.py +++ b/backend/app/utils/toolkit/google_calendar_toolkit.py @@ -1,11 +1,12 @@ from typing import Any, Dict, List from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit from camel.toolkits import GoogleCalendarToolkit as BaseGoogleCalendarToolkit +@auto_listen_toolkit(BaseGoogleCalendarToolkit) class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): agent_name: str = Agents.social_medium_agent @@ -13,44 +14,6 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): self.api_task_id = api_task_id super().__init__(timeout) - @listen_toolkit(BaseGoogleCalendarToolkit.create_event) - def create_event( - self, - event_title: str, - start_time: str, - end_time: str, - description: str = "", - location: str = "", - attendees_email: List[str] | None = None, - timezone: str = "UTC", - ) -> Dict[str, Any]: - return super().create_event(event_title, start_time, end_time, description, location, attendees_email, timezone) - - @listen_toolkit(BaseGoogleCalendarToolkit.get_events) - def get_events(self, max_results: int = 10, time_min: str | None = None) -> List[Dict[str, Any]] | Dict[str, Any]: - return super().get_events(max_results, time_min) - - @listen_toolkit(BaseGoogleCalendarToolkit.update_event) - def update_event( - self, - event_id: str, - event_title: str | None = None, - start_time: str | None = None, - end_time: str | None = None, - description: str | None = None, - location: str | None = None, - attendees_email: List[str] | None = None, - ) -> Dict[str, Any]: - return super().update_event(event_id, event_title, start_time, end_time, description, location, attendees_email) - - @listen_toolkit(BaseGoogleCalendarToolkit.delete_event) - def delete_event(self, event_id: str) -> str: - return super().delete_event(event_id) - - @listen_toolkit(BaseGoogleCalendarToolkit.get_calendar_details) - def get_calendar_details(self) -> Dict[str, Any]: - return super().get_calendar_details() - @classmethod def get_can_use_tools(cls, api_task_id: str): if env("GOOGLE_CLIENT_ID") and env("GOOGLE_CLIENT_SECRET"): diff --git a/backend/app/utils/toolkit/human_toolkit.py b/backend/app/utils/toolkit/human_toolkit.py index ba616b5c3..612548195 100644 --- a/backend/app/utils/toolkit/human_toolkit.py +++ b/backend/app/utils/toolkit/human_toolkit.py @@ -3,12 +3,13 @@ from camel.toolkits.base import BaseToolkit from loguru import logger from camel.toolkits.function_tool import FunctionTool from app.service.task import Action, ActionAskData, ActionNoticeData, get_task_lock -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit from app.service.task import process_task # Rewrite HumanToolkit because the system's user interaction was using console, but in electron we cannot use console. Changed to use SSE response to let frontend show dialog for user interaction +@auto_listen_toolkit(BaseToolkit) class HumanToolkit(BaseToolkit, AbstractToolkit): r"""A class representing a toolkit for human interaction. Note: diff --git a/backend/app/utils/toolkit/hybrid_browser_python_toolkit.py b/backend/app/utils/toolkit/hybrid_browser_python_toolkit.py index 911a6dd87..a9dd83033 100644 --- a/backend/app/utils/toolkit/hybrid_browser_python_toolkit.py +++ b/backend/app/utils/toolkit/hybrid_browser_python_toolkit.py @@ -16,7 +16,7 @@ from loguru import logger from app.component.environment import env from app.exception.exception import ProgramException from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit @@ -124,6 +124,7 @@ class BrowserSession(BaseHybridBrowserSession): break +@auto_listen_toolkit(BaseHybridBrowserToolkit) class HybridBrowserPythonToolkit(BaseHybridBrowserToolkit, AbstractToolkit): agent_name: str = Agents.search_agent @@ -224,14 +225,6 @@ class HybridBrowserPythonToolkit(BaseHybridBrowserToolkit, AbstractToolkit): self._agent: PlaywrightLLMAgent | None = None self._unified_script = self._load_unified_analyzer() - @listen_toolkit(BaseHybridBrowserToolkit.browser_open) - async def browser_open(self) -> Dict[str, str]: - return await super().browser_open() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_close) - async def browser_close(self) -> str: - return await super().browser_close() - @listen_toolkit(BaseHybridBrowserToolkit.browser_visit_page, lambda _, url: url) async def browser_visit_page(self, url: str) -> Dict[str, Any]: r"""Navigates to a URL. @@ -282,66 +275,6 @@ class HybridBrowserPythonToolkit(BaseHybridBrowserToolkit, AbstractToolkit): return {"result": nav_result, "snapshot": snapshot, **tab_info} - @listen_toolkit(BaseHybridBrowserToolkit.browser_back) - async def browser_back(self) -> Dict[str, Any]: - return await super().browser_back() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_forward) - async def browser_forward(self) -> Dict[str, Any]: - return await super().browser_forward() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_click) - async def browser_click(self, *, ref: str) -> Dict[str, Any]: - return await super().browser_click(ref=ref) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_type) - async def browser_type(self, *, ref: str, text: str) -> Dict[str, Any]: - return await super().browser_type(ref=ref, text=text) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_switch_tab) - async def browser_switch_tab(self, *, tab_id: str) -> Dict[str, Any]: - return await super().browser_switch_tab(tab_id=tab_id) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_select) - async def browser_select(self, *, ref: str, value: str) -> Dict[str, str]: - return await super().browser_select(ref=ref, value=value) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_scroll) - async def browser_scroll(self, *, direction: str, amount: int) -> Dict[str, str]: - return await super().browser_scroll(direction=direction, amount=amount) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_wait_user) - async def browser_wait_user(self, timeout_sec: float | None = None) -> Dict[str, str]: - return await super().browser_wait_user(timeout_sec) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_enter) - async def browser_enter(self) -> Dict[str, str]: - return await super().browser_enter() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_solve_task) - async def browser_solve_task(self, task_prompt: str, start_url: str, max_steps: int = 15) -> str: - return await super().browser_solve_task(task_prompt, start_url, max_steps) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_snapshot) - async def browser_get_page_snapshot(self) -> str: - return await super().browser_get_page_snapshot() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_get_som_screenshot) - async def browser_get_som_screenshot(self): - return await super().browser_get_som_screenshot() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_links) - async def browser_get_page_links(self, *, ref: List[str]) -> Dict[str, Any]: - return await super().browser_get_page_links(ref=ref) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_close_tab) - async def browser_close_tab(self, *, tab_id: str) -> Dict[str, Any]: - return await super().browser_close_tab(tab_id=tab_id) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_get_tab_info) - async def browser_get_tab_info(self) -> Dict[str, Any]: - return await super().browser_get_tab_info() - @classmethod def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: browser = HybridBrowserPythonToolkit( diff --git a/backend/app/utils/toolkit/hybrid_browser_toolkit.py b/backend/app/utils/toolkit/hybrid_browser_toolkit.py index adf6c3d0c..94a288fe0 100644 --- a/backend/app/utils/toolkit/hybrid_browser_toolkit.py +++ b/backend/app/utils/toolkit/hybrid_browser_toolkit.py @@ -16,7 +16,7 @@ from camel.toolkits.hybrid_browser_toolkit.ws_wrapper import WebSocketBrowserWra from app.component.command import bun, uv from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit @@ -210,6 +210,7 @@ class WebSocketConnectionPool: websocket_connection_pool = WebSocketConnectionPool() +@auto_listen_toolkit(BaseHybridBrowserToolkit) class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit): agent_name: str = Agents.search_agent @@ -240,7 +241,10 @@ class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit): cdp_keep_current_page: bool = False, full_visual_mode: bool = False, ) -> None: + logger.info(f"[HybridBrowserToolkit] Initializing with api_task_id: {api_task_id}") self.api_task_id = api_task_id + logger.debug(f"[HybridBrowserToolkit] api_task_id set to: {self.api_task_id}") + logger.debug(f"[HybridBrowserToolkit] Calling super().__init__ with session_id: {session_id}") super().__init__( headless=headless, user_data_dir=user_data_dir, @@ -264,16 +268,20 @@ class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit): cdp_keep_current_page=cdp_keep_current_page, full_visual_mode=full_visual_mode, ) + logger.info(f"[HybridBrowserToolkit] Initialization complete for api_task_id: {self.api_task_id}") async def _ensure_ws_wrapper(self): """Ensure WebSocket wrapper is initialized using connection pool.""" + logger.debug(f"[HybridBrowserToolkit] _ensure_ws_wrapper called for api_task_id: {getattr(self, 'api_task_id', 'NOT SET')}") global websocket_connection_pool # Get session ID from config or use default session_id = self._ws_config.get("session_id", "default") + logger.debug(f"[HybridBrowserToolkit] Using session_id: {session_id}") # Get or create connection from pool self._ws_wrapper = await websocket_connection_pool.get_connection(session_id, self._ws_config) + logger.info(f"[HybridBrowserToolkit] WebSocket wrapper initialized for session: {session_id}") # Additional health check if self._ws_wrapper.websocket is None: @@ -336,74 +344,3 @@ class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit): if hasattr(self, "_ws_wrapper") and self._ws_wrapper: session_id = self._ws_config.get("session_id", "default") logger.debug(f"HybridBrowserToolkit for session {session_id} is being garbage collected") - - @listen_toolkit(BaseHybridBrowserToolkit.browser_open) - async def browser_open(self) -> Dict[str, Any]: - return await super().browser_open() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_close) - async def browser_close(self) -> str: - return await super().browser_close() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_visit_page) - async def browser_visit_page(self, url: str) -> Dict[str, Any]: - logger.debug(f"browser_visit_page called with URL: {url}") - try: - result = await super().browser_visit_page(url) - logger.debug(f"browser_visit_page succeeded for URL: {url}") - return result - except Exception as e: - logger.error(f"browser_visit_page failed for URL {url}: {type(e).__name__}: {e}") - raise - - @listen_toolkit(BaseHybridBrowserToolkit.browser_back) - async def browser_back(self) -> Dict[str, Any]: - return await super().browser_back() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_forward) - async def browser_forward(self) -> Dict[str, Any]: - return await super().browser_forward() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_snapshot) - async def browser_get_page_snapshot(self) -> str: - return await super().browser_get_page_snapshot() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_get_som_screenshot) - async def browser_get_som_screenshot(self, read_image: bool = False, instruction: str | None = None) -> str: - return await super().browser_get_som_screenshot(read_image, instruction) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_click) - async def browser_click(self, *, ref: str) -> Dict[str, Any]: - return await super().browser_click(ref=ref) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_type) - async def browser_type(self, *, ref: str, text: str) -> Dict[str, Any]: - return await super().browser_type(ref=ref, text=text) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_select) - async def browser_select(self, *, ref: str, value: str) -> Dict[str, Any]: - return await super().browser_select(ref=ref, value=value) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_scroll) - async def browser_scroll(self, *, direction: str, amount: int = 500) -> Dict[str, Any]: - return await super().browser_scroll(direction=direction, amount=amount) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_enter) - async def browser_enter(self) -> Dict[str, Any]: - return await super().browser_enter() - - @listen_toolkit(BaseHybridBrowserToolkit.browser_wait_user) - async def browser_wait_user(self, timeout_sec: float | None = None) -> Dict[str, Any]: - return await super().browser_wait_user(timeout_sec) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_switch_tab) - async def browser_switch_tab(self, *, tab_id: str) -> Dict[str, Any]: - return await super().browser_switch_tab(tab_id=tab_id) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_close_tab) - async def browser_close_tab(self, *, tab_id: str) -> Dict[str, Any]: - return await super().browser_close_tab(tab_id=tab_id) - - @listen_toolkit(BaseHybridBrowserToolkit.browser_get_tab_info) - async def browser_get_tab_info(self) -> Dict[str, Any]: - return await super().browser_get_tab_info() diff --git a/backend/app/utils/toolkit/image_analysis_toolkit.py b/backend/app/utils/toolkit/image_analysis_toolkit.py index b325d904e..609f3045f 100644 --- a/backend/app/utils/toolkit/image_analysis_toolkit.py +++ b/backend/app/utils/toolkit/image_analysis_toolkit.py @@ -2,10 +2,11 @@ from camel.models import BaseModelBackend from camel.toolkits import ImageAnalysisToolkit as BaseImageAnalysisToolkit from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseImageAnalysisToolkit) class ImageAnalysisToolkit(BaseImageAnalysisToolkit, AbstractToolkit): agent_name: str = Agents.multi_modal_agent @@ -17,24 +18,3 @@ class ImageAnalysisToolkit(BaseImageAnalysisToolkit, AbstractToolkit): ): super().__init__(model, timeout) self.api_task_id = api_task_id - - @listen_toolkit( - BaseImageAnalysisToolkit.image_to_text, - lambda _, - image_path, - sys_prompt: f"transcribe image from {image_path} and ask sys_prompt: {sys_prompt}", - ) - def image_to_text(self, image_path: str, sys_prompt: str | None = None) -> str: - return super().image_to_text(image_path, sys_prompt) - - @listen_toolkit( - BaseImageAnalysisToolkit.ask_question_about_image, - lambda _, - image_path, - question, - sys_prompt: f"transcribe image from {image_path} and ask question: {question} with sys_prompt: {sys_prompt}", - ) - def ask_question_about_image( - self, image_path: str, question: str, sys_prompt: str | None = None - ) -> str: - return super().ask_question_about_image(image_path, question, sys_prompt) diff --git a/backend/app/utils/toolkit/linkedin_toolkit.py b/backend/app/utils/toolkit/linkedin_toolkit.py index 9b30392c0..60c39d111 100644 --- a/backend/app/utils/toolkit/linkedin_toolkit.py +++ b/backend/app/utils/toolkit/linkedin_toolkit.py @@ -2,10 +2,11 @@ from camel.toolkits import LinkedInToolkit as BaseLinkedInToolkit from camel.toolkits.function_tool import FunctionTool from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseLinkedInToolkit) class LinkedInToolkit(BaseLinkedInToolkit, AbstractToolkit): agent_name: str = Agents.social_medium_agent @@ -13,27 +14,6 @@ class LinkedInToolkit(BaseLinkedInToolkit, AbstractToolkit): super().__init__(timeout) self.api_task_id = api_task_id - @listen_toolkit( - BaseLinkedInToolkit.create_post, - lambda _, text: f"create a LinkedIn post with text: {text}", - ) - def create_post(self, text: str) -> dict: - return super().create_post(text) - - @listen_toolkit( - BaseLinkedInToolkit.delete_post, - lambda _, post_id: f"delete LinkedIn post with id: {post_id}", - ) - def delete_post(self, post_id: str) -> str: - return super().delete_post(post_id) - - @listen_toolkit( - BaseLinkedInToolkit.get_profile, - lambda _, include_id: f"get LinkedIn profile with include_id: {include_id}", - ) - def get_profile(self, include_id: bool = False) -> dict: - return super().get_profile(include_id) - @classmethod def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: if env("LINKEDIN_ACCESS_TOKEN"): diff --git a/backend/app/utils/toolkit/markitdown_toolkit.py b/backend/app/utils/toolkit/markitdown_toolkit.py index 1c1cc3528..6ac09c4ea 100644 --- a/backend/app/utils/toolkit/markitdown_toolkit.py +++ b/backend/app/utils/toolkit/markitdown_toolkit.py @@ -2,17 +2,14 @@ from typing import Dict, List from camel.toolkits import MarkItDownToolkit as BaseMarkItDownToolkit from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseMarkItDownToolkit) class MarkItDownToolkit(BaseMarkItDownToolkit, AbstractToolkit): agent_name: str = Agents.document_agent def __init__(self, api_task_id: str, timeout: float | None = None): self.api_task_id = api_task_id super().__init__(timeout) - - @listen_toolkit(BaseMarkItDownToolkit.read_files) - def read_files(self, file_paths: List[str]) -> Dict[str, str]: - return super().read_files(file_paths) diff --git a/backend/app/utils/toolkit/note_taking_toolkit.py b/backend/app/utils/toolkit/note_taking_toolkit.py index cd762bde9..5f6c2af33 100644 --- a/backend/app/utils/toolkit/note_taking_toolkit.py +++ b/backend/app/utils/toolkit/note_taking_toolkit.py @@ -5,10 +5,11 @@ from typing import Optional from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseNoteTakingToolkit) class NoteTakingToolkit(BaseNoteTakingToolkit, AbstractToolkit): agent_name: str = Agents.document_agent @@ -25,19 +26,3 @@ class NoteTakingToolkit(BaseNoteTakingToolkit, AbstractToolkit): if working_directory is None: working_directory = env("file_save_path", os.path.expanduser("~/.eigent/notes")) + "/note.md" super().__init__(working_directory=working_directory, timeout=timeout) - - @listen_toolkit(BaseNoteTakingToolkit.append_note) - def append_note(self, note_name: str, content: str) -> str: - return super().append_note(note_name=note_name, content=content) - - @listen_toolkit(BaseNoteTakingToolkit.read_note) - def read_note(self, note_name: Optional[str] = "all_notes") -> str: - return super().read_note(note_name=note_name) - - @listen_toolkit(BaseNoteTakingToolkit.create_note) - def create_note(self, note_name: str, content: str, overwrite: bool = False) -> str: - return super().create_note(note_name=note_name, content=content, overwrite=overwrite) - - @listen_toolkit(BaseNoteTakingToolkit.list_note) - def list_note(self) -> str: - return super().list_note() diff --git a/backend/app/utils/toolkit/notion_toolkit.py b/backend/app/utils/toolkit/notion_toolkit.py index 25e6ac9aa..bdc74d439 100644 --- a/backend/app/utils/toolkit/notion_toolkit.py +++ b/backend/app/utils/toolkit/notion_toolkit.py @@ -3,10 +3,11 @@ from camel.toolkits import NotionToolkit as BaseNotionToolkit from camel.toolkits.function_tool import FunctionTool from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseNotionToolkit) class NotionToolkit(BaseNotionToolkit, AbstractToolkit): agent_name: str = Agents.document_agent @@ -19,29 +20,6 @@ class NotionToolkit(BaseNotionToolkit, AbstractToolkit): super().__init__(notion_token, timeout) self.api_task_id = api_task_id - @listen_toolkit( - BaseNotionToolkit.list_all_pages, - lambda _: "list all pages in Notion workspace", - lambda result: f"{len(result)} pages found", - ) - def list_all_pages(self) -> List[dict]: - return super().list_all_pages() - - @listen_toolkit( - BaseNotionToolkit.list_all_users, - lambda _: "list all users in Notion workspace", - lambda result: f"{len(result)} users found", - ) - def list_all_users(self) -> List[dict]: - return super().list_all_users() - - @listen_toolkit( - BaseNotionToolkit.get_notion_block_text_content, - lambda _, page_id: f"get text content of page with id: {page_id}", - ) - def get_notion_block_text_content(self, block_id: str) -> str: - return super().get_notion_block_text_content(block_id) - @classmethod def get_can_use_tools(cls, api_task_id: str) -> List[FunctionTool]: if env("NOTION_TOKEN"): diff --git a/backend/app/utils/toolkit/openai_image_toolkit.py b/backend/app/utils/toolkit/openai_image_toolkit.py index f76b0a234..e6d9c3d98 100644 --- a/backend/app/utils/toolkit/openai_image_toolkit.py +++ b/backend/app/utils/toolkit/openai_image_toolkit.py @@ -3,11 +3,12 @@ from camel.toolkits import OpenAIImageToolkit as BaseOpenAIImageToolkit from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit from typing import Literal, Optional, Union, List +@auto_listen_toolkit(BaseOpenAIImageToolkit) class OpenAIImageToolkit(BaseOpenAIImageToolkit, AbstractToolkit): agent_name: str = Agents.multi_modal_agent diff --git a/backend/app/utils/toolkit/pptx_toolkit.py b/backend/app/utils/toolkit/pptx_toolkit.py index 6a06d776e..0e341d246 100644 --- a/backend/app/utils/toolkit/pptx_toolkit.py +++ b/backend/app/utils/toolkit/pptx_toolkit.py @@ -4,11 +4,12 @@ from camel.toolkits import PPTXToolkit as BasePPTXToolkit from app.component.environment import env from app.service.task import ActionWriteFileData, Agents, get_task_lock -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit from app.service.task import process_task +@auto_listen_toolkit(BasePPTXToolkit) class PPTXToolkit(BasePPTXToolkit, AbstractToolkit): agent_name: str = Agents.document_agent diff --git a/backend/app/utils/toolkit/pyautogui_toolkit.py b/backend/app/utils/toolkit/pyautogui_toolkit.py index e6d26a72e..8cf3d0c50 100644 --- a/backend/app/utils/toolkit/pyautogui_toolkit.py +++ b/backend/app/utils/toolkit/pyautogui_toolkit.py @@ -4,10 +4,11 @@ from camel.toolkits import PyAutoGUIToolkit as BasePyAutoGUIToolkit from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BasePyAutoGUIToolkit) class PyAutoGUIToolkit(BasePyAutoGUIToolkit, AbstractToolkit): agent_name: str = Agents.search_agent @@ -21,69 +22,3 @@ class PyAutoGUIToolkit(BasePyAutoGUIToolkit, AbstractToolkit): screenshots_dir = env("file_save_path", os.path.expanduser("~/Downloads")) super().__init__(timeout, screenshots_dir) self.api_task_id = api_task_id - - @listen_toolkit(BasePyAutoGUIToolkit.mouse_move, lambda _, x, y: f"mouse move to {x}, {y}") - def mouse_move(self, x: int, y: int) -> str: - return super().mouse_move(x, y) - - @listen_toolkit( - BasePyAutoGUIToolkit.mouse_click, - lambda _, button="left", clicks=1, x=None, y=None: f"mouse click {button} {clicks} times at {x}, {y}", - ) - def mouse_click( - self, - button: Literal["left", "middle", "right"] = "left", - clicks: int = 1, - x: int | None = None, - y: int | None = None, - ) -> str: - return super().mouse_click(button, clicks, x, y) - - @listen_toolkit( - BasePyAutoGUIToolkit.keyboard_type, - lambda _, text, interval=0: f"keyboard type {text}, interval {interval}", - ) - def keyboard_type(self, text: str, interval: float = 0) -> str: - return super().keyboard_type(text, interval) - - @listen_toolkit(BasePyAutoGUIToolkit.take_screenshot) - def take_screenshot(self) -> str: - return super().take_screenshot() - - @listen_toolkit(BasePyAutoGUIToolkit.get_mouse_position) - def get_mouse_position(self) -> str: - return super().get_mouse_position() - - @listen_toolkit(BasePyAutoGUIToolkit.press_key, lambda _, key: f"press key {key}") - def press_key(self, key: str | list[str]) -> str: - return super().press_key(key) - - @listen_toolkit(BasePyAutoGUIToolkit.hotkey, lambda _, keys: f"hotkey {keys}") - def hotkey(self, keys: List[str]) -> str: - return super().hotkey(keys) - - @listen_toolkit( - BasePyAutoGUIToolkit.mouse_drag, - lambda _, - start_x, - start_y, - end_x, - end_y, - button="left": f"mouse drag from {start_x}, {start_y} to {end_x}, {end_y} with {button} button", - ) - def mouse_drag( - self, - start_x: int, - start_y: int, - end_x: int, - end_y: int, - button: Literal["left", "middle", "right"] = "left", - ) -> str: - return super().mouse_drag(start_x, start_y, end_x, end_y, button) - - @listen_toolkit( - BasePyAutoGUIToolkit.scroll, - lambda _, scroll_amount, x=None, y=None: f"scroll {scroll_amount} at {x}, {y}", - ) - def scroll(self, scroll_amount: int, x: int | None = None, y: int | None = None) -> str: - return super().scroll(scroll_amount, x, y) diff --git a/backend/app/utils/toolkit/reddit_toolkit.py b/backend/app/utils/toolkit/reddit_toolkit.py index fbc471e92..46d4ddd3e 100644 --- a/backend/app/utils/toolkit/reddit_toolkit.py +++ b/backend/app/utils/toolkit/reddit_toolkit.py @@ -3,10 +3,11 @@ from camel.toolkits import RedditToolkit as BaseRedditToolkit from camel.toolkits.function_tool import FunctionTool from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseRedditToolkit) class RedditToolkit(BaseRedditToolkit, AbstractToolkit): agent_name: str = Agents.social_medium_agent @@ -20,47 +21,6 @@ class RedditToolkit(BaseRedditToolkit, AbstractToolkit): super().__init__(retries, delay, timeout) self.api_task_id = api_task_id - @listen_toolkit( - BaseRedditToolkit.collect_top_posts, - lambda _, - subreddit_name, - post_limit=5, - comment_limit=5: f"collect top posts from subreddit: {subreddit_name} with post limit: {post_limit} and comment limit: {comment_limit}", - lambda result: f"top posts collected: {result}", - ) - def collect_top_posts( - self, subreddit_name: str, post_limit: int = 5, comment_limit: int = 5 - ) -> List[Dict[str, Any]] | str: - return super().collect_top_posts(subreddit_name, post_limit, comment_limit) - - @listen_toolkit( - BaseRedditToolkit.perform_sentiment_analysis, - lambda _, data: f"perform sentiment analysis on data number: {len(data)}", - lambda result: f"perform analysis result: {result}", - ) - def perform_sentiment_analysis(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - return super().perform_sentiment_analysis(data) - - @listen_toolkit( - BaseRedditToolkit.track_keyword_discussions, - lambda _, - subreddits, - keywords, - post_limit=10, - comment_limit=10, - sentiment_analysis=False: f"track keyword discussions for subreddits: {subreddits}, keywords: {keywords}", - lambda result: f"track keyword discussions result: {result}", - ) - def track_keyword_discussions( - self, - subreddits: List[str], - keywords: List[str], - post_limit: int = 10, - comment_limit: int = 10, - sentiment_analysis: bool = False, - ) -> List[Dict[str, Any]] | str: - return super().track_keyword_discussions(subreddits, keywords, post_limit, comment_limit, sentiment_analysis) - @classmethod def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: if env("REDDIT_CLIENT_ID") and env("REDDIT_CLIENT_SECRET") and env("REDDIT_USER_AGENT"): diff --git a/backend/app/utils/toolkit/screenshot_toolkit.py b/backend/app/utils/toolkit/screenshot_toolkit.py index 1e51dabbd..4e475ef28 100644 --- a/backend/app/utils/toolkit/screenshot_toolkit.py +++ b/backend/app/utils/toolkit/screenshot_toolkit.py @@ -3,10 +3,11 @@ from camel.toolkits import ScreenshotToolkit as BaseScreenshotToolkit from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseScreenshotToolkit) class ScreenshotToolkit(BaseScreenshotToolkit, AbstractToolkit): agent_name: str = Agents.developer_agent @@ -15,13 +16,3 @@ class ScreenshotToolkit(BaseScreenshotToolkit, AbstractToolkit): if working_directory is None: working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) super().__init__(working_directory, timeout) - - @listen_toolkit(BaseScreenshotToolkit.take_screenshot_and_read_image) - def take_screenshot_and_read_image( - self, filename: str, save_to_file: bool = True, read_image: bool = True, instruction: str | None = None - ) -> str: - return super().take_screenshot_and_read_image(filename, save_to_file, read_image, instruction) - - @listen_toolkit(BaseScreenshotToolkit.read_image) - def read_image(self, image_path: str, instruction: str = "") -> str: - return super().read_image(image_path, instruction) diff --git a/backend/app/utils/toolkit/search_toolkit.py b/backend/app/utils/toolkit/search_toolkit.py index f32d196a5..2da2fe13c 100644 --- a/backend/app/utils/toolkit/search_toolkit.py +++ b/backend/app/utils/toolkit/search_toolkit.py @@ -5,10 +5,11 @@ import httpx from loguru import logger from app.component.environment import env, env_not_empty from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseSearchToolkit) class SearchToolkit(BaseSearchToolkit, AbstractToolkit): agent_name: str = Agents.search_agent diff --git a/backend/app/utils/toolkit/slack_toolkit.py b/backend/app/utils/toolkit/slack_toolkit.py index 920047a5f..966146a47 100644 --- a/backend/app/utils/toolkit/slack_toolkit.py +++ b/backend/app/utils/toolkit/slack_toolkit.py @@ -3,10 +3,11 @@ from camel.toolkits.function_tool import FunctionTool from loguru import logger from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseSlackToolkit) class SlackToolkit(BaseSlackToolkit, AbstractToolkit): agent_name: str = Agents.social_medium_agent @@ -14,71 +15,6 @@ class SlackToolkit(BaseSlackToolkit, AbstractToolkit): super().__init__(timeout) self.api_task_id = api_task_id - @listen_toolkit( - BaseSlackToolkit.create_slack_channel, - lambda _, name, is_private=True: f"create a Slack channel with name: {name} and is_private: {is_private}", - ) - def create_slack_channel(self, name: str, is_private: bool | None = True) -> str: - return super().create_slack_channel(name, is_private) - - @listen_toolkit( - BaseSlackToolkit.join_slack_channel, - lambda _, channel_id: f"join Slack channel with id: {channel_id}", - ) - def join_slack_channel(self, channel_id: str) -> str: - return super().join_slack_channel(channel_id) - - @listen_toolkit( - BaseSlackToolkit.leave_slack_channel, - lambda _, channel_id: f"leave Slack channel with id: {channel_id}", - ) - def leave_slack_channel(self, channel_id: str) -> str: - return super().leave_slack_channel(channel_id) - - @listen_toolkit( - BaseSlackToolkit.get_slack_channel_information, - lambda _: "get Slack channel information", - ) - def get_slack_channel_information(self) -> str: - return super().get_slack_channel_information() - - @listen_toolkit( - BaseSlackToolkit.get_slack_channel_message, - lambda _, channel_id: f"get Slack channel message for channel id: {channel_id}", - ) - def get_slack_channel_message(self, channel_id: str) -> str: - return super().get_slack_channel_message(channel_id) - - @listen_toolkit( - BaseSlackToolkit.send_slack_message, - lambda _, message, channel_id, file_path=None, user=None: f"send Slack message: {message} to channel id: {channel_id}, file: {file_path}, user: {user}", - ) - def send_slack_message(self, message: str, channel_id: str, file_path: str | None = None, user: str | None = None) -> str: - return super().send_slack_message(message, channel_id, file_path, user) - - @listen_toolkit( - BaseSlackToolkit.delete_slack_message, - lambda _, - time_stamp, - channel_id: f"delete Slack message with timestamp: {time_stamp} in channel id: {channel_id}", - ) - def delete_slack_message(self, time_stamp: str, channel_id: str) -> str: - return super().delete_slack_message(time_stamp, channel_id) - - @listen_toolkit( - BaseSlackToolkit.get_slack_user_list, - lambda _: "get Slack user list", - ) - def get_slack_user_list(self) -> str: - return super().get_slack_user_list() - - @listen_toolkit( - BaseSlackToolkit.get_slack_user_info, - lambda _, user_id: f"get Slack user info with user id: {user_id}", - ) - def get_slack_user_info(self, user_id: str) -> str: - return super().get_slack_user_info(user_id) - @classmethod def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: logger.debug(f"slack===={env('SLACK_BOT_TOKEN')}") diff --git a/backend/app/utils/toolkit/terminal_toolkit.py b/backend/app/utils/toolkit/terminal_toolkit.py index c4ff77faa..64e65cb67 100644 --- a/backend/app/utils/toolkit/terminal_toolkit.py +++ b/backend/app/utils/toolkit/terminal_toolkit.py @@ -4,11 +4,12 @@ from camel.toolkits.terminal_toolkit import TerminalToolkit as BaseTerminalToolk from camel.toolkits.terminal_toolkit.terminal_toolkit import _to_plain from app.component.environment import env from app.service.task import Action, ActionTerminalData, Agents, get_task_lock -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit from app.service.task import process_task +@auto_listen_toolkit(BaseTerminalToolkit) class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit): agent_name: str = Agents.developer_agent @@ -67,45 +68,3 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit): ) if hasattr(task_lock, "add_background_task"): task_lock.add_background_task(task) - - @listen_toolkit( - BaseTerminalToolkit.shell_exec, - lambda _, id, command, block=True: f"id: {id}, command: {command}, block: {block}", - ) - def shell_exec(self, id: str, command: str, block: bool = True) -> str: - return super().shell_exec(id=id, command=command, block=block) - - @listen_toolkit( - BaseTerminalToolkit.shell_view, - lambda _, id: f"id: {id}", - ) - def shell_view(self, id: str) -> str: - return super().shell_view(id) - - @listen_toolkit( - BaseTerminalToolkit.shell_wait, - lambda _, id, wait_seconds=None: f"id: {id}, wait_seconds: {wait_seconds}", - ) - def shell_wait(self, id: str, wait_seconds: float = 5.0) -> str: - return super().shell_wait(id=id, wait_seconds=wait_seconds) - - @listen_toolkit( - BaseTerminalToolkit.shell_write_to_process, - lambda _, id, command: f"id: {id}, command: {command}", - ) - def shell_write_to_process(self, id: str, command: str) -> str: - return super().shell_write_to_process(id=id, command=command) - - @listen_toolkit( - BaseTerminalToolkit.shell_kill_process, - lambda _, id: f"id: {id}", - ) - def shell_kill_process(self, id: str) -> str: - return super().shell_kill_process(id=id) - - @listen_toolkit( - BaseTerminalToolkit.shell_ask_user_for_help, - lambda _, id, prompt: f"id: {id}, prompt: {prompt}", - ) - def shell_ask_user_for_help(self, id: str, prompt: str) -> str: - return super().shell_ask_user_for_help(id=id, prompt=prompt) diff --git a/backend/app/utils/toolkit/thinking_toolkit.py b/backend/app/utils/toolkit/thinking_toolkit.py index 593e79bd2..ae7f8a62a 100644 --- a/backend/app/utils/toolkit/thinking_toolkit.py +++ b/backend/app/utils/toolkit/thinking_toolkit.py @@ -1,40 +1,13 @@ from camel.toolkits import ThinkingToolkit as BaseThinkingToolkit -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseThinkingToolkit) class ThinkingToolkit(BaseThinkingToolkit, AbstractToolkit): def __init__(self, api_task_id: str, agent_name: str, timeout: float | None = None): super().__init__(timeout) self.api_task_id = api_task_id self.agent_name = agent_name - - @listen_toolkit(BaseThinkingToolkit.plan) - def plan(self, plan: str) -> str: - return super().plan(plan) - - @listen_toolkit(BaseThinkingToolkit.hypothesize) - def hypothesize(self, hypothesis: str) -> str: - return super().hypothesize(hypothesis) - - @listen_toolkit(BaseThinkingToolkit.think) - def think(self, thought: str) -> str: - return super().think(thought) - - @listen_toolkit(BaseThinkingToolkit.contemplate) - def contemplate(self, contemplation: str) -> str: - return super().contemplate(contemplation) - - @listen_toolkit(BaseThinkingToolkit.critique) - def critique(self, critique: str) -> str: - return super().critique(critique) - - @listen_toolkit(BaseThinkingToolkit.synthesize) - def synthesize(self, synthesis: str) -> str: - return super().synthesize(synthesis) - - @listen_toolkit(BaseThinkingToolkit.reflect) - def reflect(self, reflection: str) -> str: - return super().reflect(reflection) diff --git a/backend/app/utils/toolkit/twitter_toolkit.py b/backend/app/utils/toolkit/twitter_toolkit.py index 77c4401ea..c90b9f547 100644 --- a/backend/app/utils/toolkit/twitter_toolkit.py +++ b/backend/app/utils/toolkit/twitter_toolkit.py @@ -9,10 +9,11 @@ from camel.toolkits.twitter_toolkit import ( from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseTwitterToolkit) class TwitterToolkit(BaseTwitterToolkit, AbstractToolkit): agent_name: str = Agents.social_medium_agent diff --git a/backend/app/utils/toolkit/video_analysis_toolkit.py b/backend/app/utils/toolkit/video_analysis_toolkit.py index be1018f4d..08922431f 100644 --- a/backend/app/utils/toolkit/video_analysis_toolkit.py +++ b/backend/app/utils/toolkit/video_analysis_toolkit.py @@ -4,10 +4,11 @@ from camel.toolkits import VideoAnalysisToolkit as BaseVideoAnalysisToolkit from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseVideoAnalysisToolkit) class VideoAnalysisToolkit(BaseVideoAnalysisToolkit, AbstractToolkit): agent_name: str = Agents.multi_modal_agent @@ -36,10 +37,3 @@ class VideoAnalysisToolkit(BaseVideoAnalysisToolkit, AbstractToolkit): cookies_path, timeout, ) - - @listen_toolkit( - BaseVideoAnalysisToolkit.ask_question_about_video, - lambda _, video_path, question: f"transcribe video from {video_path} and ask question: {question}", - ) - def ask_question_about_video(self, video_path: str, question: str) -> str: - return super().ask_question_about_video(video_path, question) diff --git a/backend/app/utils/toolkit/video_download_toolkit.py b/backend/app/utils/toolkit/video_download_toolkit.py index f77fe1157..20641264d 100644 --- a/backend/app/utils/toolkit/video_download_toolkit.py +++ b/backend/app/utils/toolkit/video_download_toolkit.py @@ -5,10 +5,11 @@ from camel.toolkits import VideoDownloaderToolkit as BaseVideoDownloaderToolkit from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseVideoDownloaderToolkit) class VideoDownloaderToolkit(BaseVideoDownloaderToolkit, AbstractToolkit): agent_name: str = Agents.multi_modal_agent @@ -23,23 +24,3 @@ class VideoDownloaderToolkit(BaseVideoDownloaderToolkit, AbstractToolkit): working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) super().__init__(working_directory, cookies_path, timeout) self.api_task_id = api_task_id - - @listen_toolkit(BaseVideoDownloaderToolkit.download_video) - def download_video(self, url: str) -> str: - return super().download_video(url) - - @listen_toolkit( - BaseVideoDownloaderToolkit.get_video_bytes, - lambda _, video_path: f"get video bytes from {video_path}", - lambda _: "get video bytes", - ) - def get_video_bytes(self, video_path: str) -> bytes: - return super().get_video_bytes(video_path) - - @listen_toolkit( - BaseVideoDownloaderToolkit.get_video_screenshots, - lambda _, video_path, amount: f"get video screenshots from {video_path}, amount: {amount}", - lambda results: f"get video screenshots {len(results)}", - ) - def get_video_screenshots(self, video_path: str, amount: int) -> List[Image]: - return super().get_video_screenshots(video_path, amount) diff --git a/backend/app/utils/toolkit/web_deploy_toolkit.py b/backend/app/utils/toolkit/web_deploy_toolkit.py index eb40ae940..099d6cfd8 100644 --- a/backend/app/utils/toolkit/web_deploy_toolkit.py +++ b/backend/app/utils/toolkit/web_deploy_toolkit.py @@ -3,10 +3,11 @@ from typing import Any, Dict from camel.toolkits import WebDeployToolkit as BaseWebDeployToolkit from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseWebDeployToolkit) class WebDeployToolkit(BaseWebDeployToolkit, AbstractToolkit): agent_name: str = Agents.developer_agent @@ -43,11 +44,3 @@ class WebDeployToolkit(BaseWebDeployToolkit, AbstractToolkit): ) -> Dict[str, Any]: subdirectory = str(uuid.uuid4()) return super().deploy_folder(folder_path, port, domain, subdirectory) - - @listen_toolkit(BaseWebDeployToolkit.stop_server) - def stop_server(self, port: int) -> Dict[str, Any]: - return super().stop_server(port) - - @listen_toolkit(BaseWebDeployToolkit.list_running_servers) - def list_running_servers(self) -> Dict[str, Any]: - return super().list_running_servers() diff --git a/backend/app/utils/toolkit/whatsapp_toolkit.py b/backend/app/utils/toolkit/whatsapp_toolkit.py index 83dcd941a..b9cfd635b 100644 --- a/backend/app/utils/toolkit/whatsapp_toolkit.py +++ b/backend/app/utils/toolkit/whatsapp_toolkit.py @@ -3,10 +3,11 @@ from camel.toolkits import WhatsAppToolkit as BaseWhatsAppToolkit from camel.toolkits.function_tool import FunctionTool from app.component.environment import env from app.service.task import Agents -from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.listen.toolkit_listen import auto_listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit +@auto_listen_toolkit(BaseWhatsAppToolkit) class WhatsAppToolkit(BaseWhatsAppToolkit, AbstractToolkit): agent_name: str = Agents.social_medium_agent @@ -14,30 +15,6 @@ class WhatsAppToolkit(BaseWhatsAppToolkit, AbstractToolkit): super().__init__(timeout) self.api_task_id = api_task_id - @listen_toolkit( - BaseWhatsAppToolkit.send_message, - lambda _, to, message: f"send message to {to}: {message}", - lambda result: f"message sent result: {result}", - ) - def send_message(self, to: str, message: str) -> Dict[str, Any] | str: - return super().send_message(to, message) - - @listen_toolkit( - BaseWhatsAppToolkit.get_message_templates, - lambda _: "get message templates", - lambda result: f"message templates: {result}", - ) - def get_message_templates(self) -> List[Dict[str, Any]] | str: - return super().get_message_templates() - - @listen_toolkit( - BaseWhatsAppToolkit.get_business_profile, - lambda _: "get business profile", - lambda result: f"business profile: {result}", - ) - def get_business_profile(self) -> Dict[str, Any] | str: - return super().get_business_profile() - @classmethod def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: if env("WHATSAPP_ACCESS_TOKEN") and env("WHATSAPP_PHONE_NUMBER_ID"): diff --git a/electron/main/index.ts b/electron/main/index.ts index 627b52eb0..682f3d8ca 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -609,6 +609,13 @@ function registerIpcHandlers() { return { success: false, error: 'File does not exist' }; } + // Check if it's a directory + const stats = await fsp.stat(filePath); + if (stats.isDirectory()) { + log.error('Path is a directory, not a file:', filePath); + return { success: false, error: 'EISDIR: illegal operation on a directory, read' }; + } + // Read file content const fileContent = await fsp.readFile(filePath); log.info('File read successfully:', filePath); diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts index bb40cb365..e58425832 100644 --- a/src/store/chatStore.ts +++ b/src/store/chatStore.ts @@ -727,7 +727,7 @@ const chatStore = create()( if (taskIndex !== -1) { const { toolkit_name, method_name } = agentMessages.data; - if (toolkit_name && method_name) { + if (toolkit_name && method_name && assigneeAgentIndex !== -1) { const task = taskAssigning[assigneeAgentIndex].tasks.find((task: TaskInfo) => task.id === agentMessages.data.process_task_id); const message = filterMessage(agentMessages) @@ -739,7 +739,7 @@ const chatStore = create()( message: message.data.message as string, toolkitStatus: "running" as AgentStatus, } - if (assigneeAgentIndex !== -1 && task) { + if (task) { task.toolkits ??= [] task.toolkits.push({ ...toolkit }); task.status = "running"; @@ -887,9 +887,12 @@ const chatStore = create()( taskId as string ); if (!type && import.meta.env.VITE_USE_LOCAL_PROXY !== 'true' && res.length > 0) { + // Filter out directories, only upload actual files + const files = res.filter((file: any) => !file.isFolder); + // Upload files sequentially to avoid overwhelming the server const uploadResults = await Promise.allSettled( - res.map(async (file: any) => { + files.map(async (file: any) => { try { // Read file content using Electron API const result = await window.ipcRenderer.invoke('read-file', file.path);