From 1c87d3d7da09b1c93538b9bf3e526fcac0c8f82b Mon Sep 17 00:00:00 2001 From: abhishekpatil4 Date: Sun, 9 Feb 2025 20:51:57 +0530 Subject: [PATCH 1/3] feat: gmail component --- .../langflow/components/composio/__init__.py | 3 +- .../langflow/components/composio/gmail_api.py | 333 ++++++++++++++++++ src/frontend/src/icons/gmail/gmail.jsx | 4 + src/frontend/src/icons/gmail/gmail.svg | 1 + src/frontend/src/icons/gmail/index.tsx | 9 + src/frontend/src/utils/styleUtils.ts | 3 + 6 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 src/backend/base/langflow/components/composio/gmail_api.py create mode 100644 src/frontend/src/icons/gmail/gmail.jsx create mode 100644 src/frontend/src/icons/gmail/gmail.svg create mode 100644 src/frontend/src/icons/gmail/index.tsx diff --git a/src/backend/base/langflow/components/composio/__init__.py b/src/backend/base/langflow/components/composio/__init__.py index 24e438134a44..12d4ecac72af 100644 --- a/src/backend/base/langflow/components/composio/__init__.py +++ b/src/backend/base/langflow/components/composio/__init__.py @@ -1,3 +1,4 @@ from .composio_api import ComposioAPIComponent +from .gmail_api import GmailAPIComponent -__all__ = ["ComposioAPIComponent"] +__all__ = ["ComposioAPIComponent", "GmailAPIComponent"] diff --git a/src/backend/base/langflow/components/composio/gmail_api.py b/src/backend/base/langflow/components/composio/gmail_api.py new file mode 100644 index 000000000000..b8b0283a086c --- /dev/null +++ b/src/backend/base/langflow/components/composio/gmail_api.py @@ -0,0 +1,333 @@ +from collections.abc import Sequence +from typing import Any +import requests +from composio.client.collections import AppAuthScheme +from composio.client.exceptions import NoItemsFound +from composio_langchain import Action, ComposioToolSet +from langchain_core.tools import Tool +from loguru import logger +from langflow.base.langchain_utilities.model import LCToolComponent +from langflow.inputs import DropdownInput, LinkInput, MessageTextInput, MultiselectInput, SecretStrInput, StrInput +from langflow.io import Output + + +class GmailAPIComponent(LCToolComponent): + display_name: str = "Gmail" + description: str = "Use gmail actions with your agent" + name = "gmailAPI" + icon = "gmail" + documentation: str = "https://docs.composio.dev" + + inputs = [ + MessageTextInput(name="entity_id", display_name="Entity ID", value="default", advanced=True), + SecretStrInput( + name="api_key", + display_name="Composio API Key", + required=True, + info="Refer to https://docs.composio.dev/faq/api_key/api_key", + real_time_refresh=True, + ), + SecretStrInput( + name="app_credentials", + display_name="App Credentials", + required=False, + dynamic=True, + show=False, + info="Credentials for app authentication (API Key, Password, etc)", + load_from_db=False, + ), + MessageTextInput( + name="username", + display_name="Username", + required=False, + dynamic=True, + show=False, + info="Username for Basic authentication", + ), + LinkInput( + name="auth_link", + display_name="Authentication Link", + value="", + info="Click to authenticate with OAuth2", + dynamic=True, + show=False, + placeholder="Click to authenticate", + ), + StrInput( + name="auth_status", + display_name="Auth Status", + value="Not Connected", + info="Current authentication status", + dynamic=True, + show=False, + real_time_refresh=True, + ), + MultiselectInput( + name="action_names", + display_name="Actions to use", + required=True, + options=[], + value=[], + info="The actions to pass to agent to execute", + dynamic=True, + show=False, + ), + ] + + outputs = [ + Output(name="tools", display_name="Tools", method="build_tool"), + ] + + def _check_for_authorization(self, app: str) -> str: + """Checks if the app is authorized. + + Args: + app (str): The app name to check authorization for. + + Returns: + str: The authorization status or URL. + """ + toolset = self._build_wrapper() + entity = toolset.client.get_entity(id=self.entity_id) + try: + entity.get_connection(app=app) + except NoItemsFound: + auth_scheme = self._get_auth_scheme(app) + return self._handle_auth_by_scheme(entity, app, auth_scheme) + except Exception: # noqa: BLE001 + logger.exception("Authorization error") + return "Error checking authorization" + else: + return f"{app} CONNECTED" + + def _get_auth_scheme(self, app_name: str) -> AppAuthScheme: + """Get the primary auth scheme for an app. + + Args: + app_name (str): The name of the app to get auth scheme for. + + Returns: + AppAuthScheme: The auth scheme details. + """ + toolset = self._build_wrapper() + try: + return toolset.get_auth_scheme_for_app(app=app_name.lower()) + except Exception: # noqa: BLE001 + logger.exception(f"Error getting auth scheme for {app_name}") + return None + + def _get_oauth_apps(self, api_key: str) -> list[str]: + """Fetch OAuth-enabled apps from Composio API. + + Args: + api_key (str): The Composio API key. + + Returns: + list[str]: A list containing OAuth-enabled app names. + """ + oauth_apps = [] + try: + url = "https://backend.composio.dev/api/v1/apps" + headers = {"x-api-key": api_key} + params = { + "includeLocal": "true", + "additionalFields": "auth_schemes", + "sortBy": "alphabet", + } + + response = requests.get(url, headers=headers, params=params, timeout=20) + data = response.json() + + for item in data.get("items", []): + for auth_scheme in item.get("auth_schemes", []): + if auth_scheme.get("mode") in ["OAUTH1", "OAUTH2"]: + oauth_apps.append(item["key"].upper()) + break + except requests.RequestException as e: + logger.error(f"Error fetching OAuth apps: {e}") + return [] + else: + return oauth_apps + + def _handle_auth_by_scheme(self, entity: Any, app: str, auth_scheme: AppAuthScheme) -> str: + """Handle authentication based on the auth scheme. + + Args: + entity (Any): The entity instance. + app (str): The app name. + auth_scheme (AppAuthScheme): The auth scheme details. + + Returns: + str: The authentication status or URL. + """ + auth_mode = auth_scheme.auth_mode + + try: + # First check if already connected + entity.get_connection(app=app) + except NoItemsFound: + # If not connected, handle new connection based on auth mode + if auth_mode == "API_KEY": + if hasattr(self, "app_credentials") and self.app_credentials: + try: + entity.initiate_connection( + app_name=app, + auth_mode="API_KEY", + auth_config={"api_key": self.app_credentials}, + use_composio_auth=False, + force_new_integration=True, + ) + except Exception as e: # noqa: BLE001 + logger.error(f"Error connecting with API Key: {e}") + return "Invalid API Key" + else: + return f"{app} CONNECTED" + return "Enter API Key" + + if ( + auth_mode == "BASIC" + and hasattr(self, "username") + and hasattr(self, "app_credentials") + and self.username + and self.app_credentials + ): + try: + entity.initiate_connection( + app_name=app, + auth_mode="BASIC", + auth_config={"username": self.username, "password": self.app_credentials}, + use_composio_auth=False, + force_new_integration=True, + ) + except Exception as e: # noqa: BLE001 + logger.error(f"Error connecting with Basic Auth: {e}") + return "Invalid credentials" + else: + return f"{app} CONNECTED" + elif auth_mode == "BASIC": + return "Enter Username and Password" + + if auth_mode == "OAUTH2": + try: + return self._initiate_default_connection(entity, app) + except Exception as e: # noqa: BLE001 + logger.error(f"Error initiating OAuth2: {e}") + return "OAuth2 initialization failed" + + return "Unsupported auth mode" + except Exception as e: # noqa: BLE001 + logger.error(f"Error checking connection status: {e}") + return f"Error: {e!s}" + else: + return f"{app} CONNECTED" + + def _initiate_default_connection(self, entity: Any, app: str) -> str: + connection = entity.initiate_connection(app_name=app, use_composio_auth=True, force_new_integration=True) + return connection.redirectUrl + + def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict: # noqa: ARG002 + dynamic_fields = ["app_credentials", "username", "auth_link", "auth_status", "action_names"] + for field in dynamic_fields: + if field in build_config: + if build_config[field]["value"] is None or build_config[field]["value"] == "": + build_config[field]["show"] = False + build_config[field]["advanced"] = True + build_config[field]["load_from_db"] = False + else: + build_config[field]["show"] = True + build_config[field]["advanced"] = False + + if hasattr(self, "api_key") and self.api_key != "": + app_name = "gmail" + try: + toolset = self._build_wrapper() + entity = toolset.client.get_entity(id=self.entity_id) + + # Always show auth_status when app is selected + build_config["auth_status"]["show"] = True + build_config["auth_status"]["advanced"] = False + + try: + # Check if already connected + entity.get_connection(app=app_name) + build_config["auth_status"]["value"] = "✅" + build_config["auth_link"]["show"] = False + # Show action selection for connected apps + build_config["action_names"]["show"] = True + build_config["action_names"]["advanced"] = False + + except NoItemsFound: + # Get auth scheme and show relevant fields + auth_scheme = self._get_auth_scheme(app_name) + auth_mode = auth_scheme.auth_mode + logger.info(f"Auth mode for {app_name}: {auth_mode}") + + if auth_mode == "API_KEY": + build_config["app_credentials"]["show"] = True + build_config["app_credentials"]["advanced"] = False + build_config["app_credentials"]["display_name"] = "API Key" + build_config["auth_status"]["value"] = "Enter API Key" + + elif auth_mode == "BASIC": + build_config["username"]["show"] = True + build_config["username"]["advanced"] = False + build_config["app_credentials"]["show"] = True + build_config["app_credentials"]["advanced"] = False + build_config["app_credentials"]["display_name"] = "Password" + build_config["auth_status"]["value"] = "Enter Username and Password" + + elif auth_mode == "OAUTH2": + build_config["auth_link"]["show"] = True + build_config["auth_link"]["advanced"] = False + auth_url = self._initiate_default_connection(entity, app_name) + build_config["auth_link"]["value"] = auth_url + build_config["auth_status"]["value"] = "Click link to authenticate" + + else: + build_config["auth_status"]["value"] = "Unsupported auth mode" + + + if build_config["auth_status"]["value"] == "✅": + all_action_names = [str(action).replace("Action.", "") for action in Action.all()] + gmail_actions = [ + action_name + for action_name in all_action_names + if action_name.lower().startswith("gmail_") + ] + if build_config["action_names"]["options"] != gmail_actions: + build_config["action_names"]["options"] = gmail_actions + build_config["action_names"]["value"] = [gmail_actions[0]] if gmail_actions else [""] + + except Exception as e: # noqa: BLE001 + logger.error(f"Error checking auth status: {e}, app: {app_name}") + build_config["auth_status"]["value"] = f"Error: {e!s}" + + return build_config + + def build_tool(self) -> Sequence[Tool]: + """Build Composio tools based on selected actions. + + Returns: + Sequence[Tool]: List of configured Composio tools. + """ + composio_toolset = self._build_wrapper() + return composio_toolset.get_tools(actions=self.action_names) + + def _build_wrapper(self) -> ComposioToolSet: + """Build the Composio toolset wrapper. + + Returns: + ComposioToolSet: The initialized toolset. + + Raises: + ValueError: If the API key is not found or invalid. + """ + try: + if not self.api_key: + msg = "Composio API Key is required" + raise ValueError(msg) + return ComposioToolSet(api_key=self.api_key) + except ValueError as e: + logger.error(f"Error building Composio wrapper: {e}") + msg = "Please provide a valid Composio API Key in the component settings" + raise ValueError(msg) from e \ No newline at end of file diff --git a/src/frontend/src/icons/gmail/gmail.jsx b/src/frontend/src/icons/gmail/gmail.jsx new file mode 100644 index 000000000000..80eeade3e45e --- /dev/null +++ b/src/frontend/src/icons/gmail/gmail.jsx @@ -0,0 +1,4 @@ +const Icon = (props) => ( + +); +export default Icon; diff --git a/src/frontend/src/icons/gmail/gmail.svg b/src/frontend/src/icons/gmail/gmail.svg new file mode 100644 index 000000000000..ad845176a0a2 --- /dev/null +++ b/src/frontend/src/icons/gmail/gmail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/src/icons/gmail/index.tsx b/src/frontend/src/icons/gmail/index.tsx new file mode 100644 index 000000000000..3e5a81c2b1aa --- /dev/null +++ b/src/frontend/src/icons/gmail/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import GmailIconSVG from "./gmail"; + +export const GmailIcon = forwardRef< + SVGSVGElement, + React.PropsWithChildren<{}> +>((props, ref) => { + return ; +}); \ No newline at end of file diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 2fcfeb84b5cf..b70f6a89780f 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -1,3 +1,4 @@ +import { GmailIcon } from "../icons/Gmail"; import { AIMLIcon } from "@/icons/AIML"; import { DuckDuckGoIcon } from "@/icons/DuckDuckGo"; import { ExaIcon } from "@/icons/Exa"; @@ -515,6 +516,7 @@ export const SIDEBAR_CATEGORIES = [ ]; export const SIDEBAR_BUNDLES = [ + { display_name: "Gmail", name: "gmail", icon: "Gmail" }, { display_name: "LangChain", name: "langchain_utilities", icon: "LangChain" }, { display_name: "AgentQL", name: "agentql", icon: "AgentQL" }, { display_name: "AssemblyAI", name: "assemblyai", icon: "AssemblyAI" }, @@ -597,6 +599,7 @@ export const nodeIconsLucide: iconsType = { ChatInput: MessagesSquare, ChatOutput: MessagesSquare, //Integration Icons + Gmail: GmailIcon, LMStudio: LMStudioIcon, Notify: Bell, ListFlows: Group, From 2bfa90777a844b8605187e03f578da8bdf735fe5 Mon Sep 17 00:00:00 2001 From: abhishekpatil4 Date: Sun, 9 Feb 2025 21:11:36 +0530 Subject: [PATCH 2/3] feat: add refresh_button to gmail component's field --- src/backend/base/langflow/components/composio/gmail_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/components/composio/gmail_api.py b/src/backend/base/langflow/components/composio/gmail_api.py index b8b0283a086c..758899a015a6 100644 --- a/src/backend/base/langflow/components/composio/gmail_api.py +++ b/src/backend/base/langflow/components/composio/gmail_api.py @@ -60,7 +60,7 @@ class GmailAPIComponent(LCToolComponent): info="Current authentication status", dynamic=True, show=False, - real_time_refresh=True, + refresh_button=True, ), MultiselectInput( name="action_names", From b88574d543245193d0d6367ec9b86ae20c5bba6b Mon Sep 17 00:00:00 2001 From: abhishekpatil4 Date: Mon, 10 Feb 2025 22:47:01 +0530 Subject: [PATCH 3/3] feat: add support for gmail native component with toolmode --- .../langflow/components/composio/gmail_api.py | 315 +++++++----------- 1 file changed, 129 insertions(+), 186 deletions(-) diff --git a/src/backend/base/langflow/components/composio/gmail_api.py b/src/backend/base/langflow/components/composio/gmail_api.py index 758899a015a6..c24cdb86141f 100644 --- a/src/backend/base/langflow/components/composio/gmail_api.py +++ b/src/backend/base/langflow/components/composio/gmail_api.py @@ -1,25 +1,33 @@ -from collections.abc import Sequence -from typing import Any -import requests +# Third-party imports from composio.client.collections import AppAuthScheme from composio.client.exceptions import NoItemsFound -from composio_langchain import Action, ComposioToolSet +from composio_langchain import Action, App, ComposioToolSet from langchain_core.tools import Tool from loguru import logger +from typing import Any + +# Local imports from langflow.base.langchain_utilities.model import LCToolComponent from langflow.inputs import DropdownInput, LinkInput, MessageTextInput, MultiselectInput, SecretStrInput, StrInput from langflow.io import Output +from langflow.schema.message import Message class GmailAPIComponent(LCToolComponent): display_name: str = "Gmail" - description: str = "Use gmail actions with your agent" - name = "gmailAPI" - icon = "gmail" + description: str = "Use Gmail API to send emails and create drafts" + name = "GmailAPI" + icon = "Gmail" documentation: str = "https://docs.composio.dev" inputs = [ - MessageTextInput(name="entity_id", display_name="Entity ID", value="default", advanced=True), + MessageTextInput( + name="entity_id", + display_name="Entity ID", + value="default", + advanced=True, + tool_mode=True, # Enable tool mode toggle + ), SecretStrInput( name="api_key", display_name="Composio API Key", @@ -27,23 +35,6 @@ class GmailAPIComponent(LCToolComponent): info="Refer to https://docs.composio.dev/faq/api_key/api_key", real_time_refresh=True, ), - SecretStrInput( - name="app_credentials", - display_name="App Credentials", - required=False, - dynamic=True, - show=False, - info="Credentials for app authentication (API Key, Password, etc)", - load_from_db=False, - ), - MessageTextInput( - name="username", - display_name="Username", - required=False, - dynamic=True, - show=False, - info="Username for Basic authentication", - ), LinkInput( name="auth_link", display_name="Authentication Link", @@ -60,105 +51,145 @@ class GmailAPIComponent(LCToolComponent): info="Current authentication status", dynamic=True, show=False, - refresh_button=True, + refresh_button=True ), - MultiselectInput( - name="action_names", - display_name="Actions to use", - required=True, + # Non-tool mode inputs - explicitly set show=True + DropdownInput( + name="action", + display_name="Action", + # options=["GMAIL_SEND_EMAIL", "GMAIL_CREATE_EMAIL_DRAFT"], options=[], - value=[], - info="The actions to pass to agent to execute", - dynamic=True, + value="", + info="Select Gmail action to perform", + show=True, + real_time_refresh=True, + ), + MessageTextInput( + name="recipient_email", + display_name="Recipient Email", + required=True, + info="Email address of the recipient", + show=False, + tool_mode=True + ), + MessageTextInput( + name="subject", + display_name="Subject", + required=True, + info="Subject of the email", show=False, + tool_mode=True, ), + MessageTextInput( + name="body", + display_name="Body", + required=True, + info="Content of the email", + show=False, + tool_mode=True, + ) ] outputs = [ - Output(name="tools", display_name="Tools", method="build_tool"), + Output( + name="text", + display_name="Result", + method="process_action" + ), ] - def _check_for_authorization(self, app: str) -> str: - """Checks if the app is authorized. + def process_action(self) -> Message: + """Process Gmail action and return result as Message.""" + toolset = self._build_wrapper() - Args: - app (str): The app name to check authorization for. + if not hasattr(self, 'action') or not hasattr(self, 'recipient_email') or not hasattr(self, 'subject') or not hasattr(self, 'body'): + msg = "Missing required fields" + raise ValueError(msg) - Returns: - str: The authorization status or URL. - """ - toolset = self._build_wrapper() - entity = toolset.client.get_entity(id=self.entity_id) try: - entity.get_connection(app=app) - except NoItemsFound: - auth_scheme = self._get_auth_scheme(app) - return self._handle_auth_by_scheme(entity, app, auth_scheme) - except Exception: # noqa: BLE001 - logger.exception("Authorization error") - return "Error checking authorization" - else: - return f"{app} CONNECTED" + enum_name = getattr(Action, self.action) + result = toolset.execute_action( + action=enum_name, + params={ + "recipient_email": self.recipient_email, + "subject": self.subject, + "body": self.body + } + ) + self.status = result + return Message(text=str(result)) + except Exception as e: + logger.error(f"Error executing action: {e}") + msg = f"Failed to execute {self.action}: {str(e)}" + raise ValueError(msg) from e + + def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict: + # Always show auth status + build_config["auth_status"]["show"] = True + build_config["auth_status"]["advanced"] = False + + # Handle action selection changes + if field_name == "action": + if field_value != "": + build_config["recipient_email"]["show"] = True + build_config["subject"]["show"] = True + build_config["body"]["show"] = True + + # Handle authentication checks if API key is present + if hasattr(self, "api_key") and self.api_key != "": + build_config["action"]["options"] = ["GMAIL_SEND_EMAIL", "GMAIL_CREATE_EMAIL_DRAFT"] + try: + toolset = self._build_wrapper() + entity = toolset.client.get_entity(id=self.entity_id) + + try: + # Check if already connected + entity.get_connection(app="gmail") + build_config["auth_status"]["value"] = "✅" + build_config["auth_link"]["show"] = False + + except NoItemsFound: + # Handle authentication + auth_scheme = self._get_auth_scheme("gmail") + if auth_scheme.auth_mode == "OAUTH2": + build_config["auth_link"]["show"] = True + build_config["auth_link"]["advanced"] = False + auth_url = self._initiate_default_connection(entity, "gmail") + build_config["auth_link"]["value"] = auth_url + build_config["auth_status"]["value"] = "Click link to authenticate" + + except Exception as e: + logger.error(f"Error checking auth status: {e}") + build_config["auth_status"]["value"] = f"Error: {e!s}" + + return build_config def _get_auth_scheme(self, app_name: str) -> AppAuthScheme: """Get the primary auth scheme for an app. Args: - app_name (str): The name of the app to get auth scheme for. + app_name (str): The name of the app to get auth scheme for. Returns: - AppAuthScheme: The auth scheme details. + AppAuthScheme: The auth scheme details. """ toolset = self._build_wrapper() try: return toolset.get_auth_scheme_for_app(app=app_name.lower()) - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 logger.exception(f"Error getting auth scheme for {app_name}") return None - def _get_oauth_apps(self, api_key: str) -> list[str]: - """Fetch OAuth-enabled apps from Composio API. - - Args: - api_key (str): The Composio API key. - - Returns: - list[str]: A list containing OAuth-enabled app names. - """ - oauth_apps = [] - try: - url = "https://backend.composio.dev/api/v1/apps" - headers = {"x-api-key": api_key} - params = { - "includeLocal": "true", - "additionalFields": "auth_schemes", - "sortBy": "alphabet", - } - - response = requests.get(url, headers=headers, params=params, timeout=20) - data = response.json() - - for item in data.get("items", []): - for auth_scheme in item.get("auth_schemes", []): - if auth_scheme.get("mode") in ["OAUTH1", "OAUTH2"]: - oauth_apps.append(item["key"].upper()) - break - except requests.RequestException as e: - logger.error(f"Error fetching OAuth apps: {e}") - return [] - else: - return oauth_apps - def _handle_auth_by_scheme(self, entity: Any, app: str, auth_scheme: AppAuthScheme) -> str: """Handle authentication based on the auth scheme. Args: - entity (Any): The entity instance. - app (str): The app name. - auth_scheme (AppAuthScheme): The auth scheme details. + entity (Any): The entity instance. + app (str): The app name. + auth_scheme (AppAuthScheme): The auth scheme details. Returns: - str: The authentication status or URL. + str: The authentication status or URL. """ auth_mode = auth_scheme.auth_mode @@ -177,7 +208,7 @@ def _handle_auth_by_scheme(self, entity: Any, app: str, auth_scheme: AppAuthSche use_composio_auth=False, force_new_integration=True, ) - except Exception as e: # noqa: BLE001 + except Exception as e: # noqa: BLE001 logger.error(f"Error connecting with API Key: {e}") return "Invalid API Key" else: @@ -199,7 +230,7 @@ def _handle_auth_by_scheme(self, entity: Any, app: str, auth_scheme: AppAuthSche use_composio_auth=False, force_new_integration=True, ) - except Exception as e: # noqa: BLE001 + except Exception as e: # noqa: BLE001 logger.error(f"Error connecting with Basic Auth: {e}") return "Invalid credentials" else: @@ -210,12 +241,12 @@ def _handle_auth_by_scheme(self, entity: Any, app: str, auth_scheme: AppAuthSche if auth_mode == "OAUTH2": try: return self._initiate_default_connection(entity, app) - except Exception as e: # noqa: BLE001 + except Exception as e: # noqa: BLE001 logger.error(f"Error initiating OAuth2: {e}") return "OAuth2 initialization failed" return "Unsupported auth mode" - except Exception as e: # noqa: BLE001 + except Exception as e: # noqa: BLE001 logger.error(f"Error checking connection status: {e}") return f"Error: {e!s}" else: @@ -225,102 +256,14 @@ def _initiate_default_connection(self, entity: Any, app: str) -> str: connection = entity.initiate_connection(app_name=app, use_composio_auth=True, force_new_integration=True) return connection.redirectUrl - def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict: # noqa: ARG002 - dynamic_fields = ["app_credentials", "username", "auth_link", "auth_status", "action_names"] - for field in dynamic_fields: - if field in build_config: - if build_config[field]["value"] is None or build_config[field]["value"] == "": - build_config[field]["show"] = False - build_config[field]["advanced"] = True - build_config[field]["load_from_db"] = False - else: - build_config[field]["show"] = True - build_config[field]["advanced"] = False - - if hasattr(self, "api_key") and self.api_key != "": - app_name = "gmail" - try: - toolset = self._build_wrapper() - entity = toolset.client.get_entity(id=self.entity_id) - - # Always show auth_status when app is selected - build_config["auth_status"]["show"] = True - build_config["auth_status"]["advanced"] = False - - try: - # Check if already connected - entity.get_connection(app=app_name) - build_config["auth_status"]["value"] = "✅" - build_config["auth_link"]["show"] = False - # Show action selection for connected apps - build_config["action_names"]["show"] = True - build_config["action_names"]["advanced"] = False - - except NoItemsFound: - # Get auth scheme and show relevant fields - auth_scheme = self._get_auth_scheme(app_name) - auth_mode = auth_scheme.auth_mode - logger.info(f"Auth mode for {app_name}: {auth_mode}") - - if auth_mode == "API_KEY": - build_config["app_credentials"]["show"] = True - build_config["app_credentials"]["advanced"] = False - build_config["app_credentials"]["display_name"] = "API Key" - build_config["auth_status"]["value"] = "Enter API Key" - - elif auth_mode == "BASIC": - build_config["username"]["show"] = True - build_config["username"]["advanced"] = False - build_config["app_credentials"]["show"] = True - build_config["app_credentials"]["advanced"] = False - build_config["app_credentials"]["display_name"] = "Password" - build_config["auth_status"]["value"] = "Enter Username and Password" - - elif auth_mode == "OAUTH2": - build_config["auth_link"]["show"] = True - build_config["auth_link"]["advanced"] = False - auth_url = self._initiate_default_connection(entity, app_name) - build_config["auth_link"]["value"] = auth_url - build_config["auth_status"]["value"] = "Click link to authenticate" - - else: - build_config["auth_status"]["value"] = "Unsupported auth mode" - - - if build_config["auth_status"]["value"] == "✅": - all_action_names = [str(action).replace("Action.", "") for action in Action.all()] - gmail_actions = [ - action_name - for action_name in all_action_names - if action_name.lower().startswith("gmail_") - ] - if build_config["action_names"]["options"] != gmail_actions: - build_config["action_names"]["options"] = gmail_actions - build_config["action_names"]["value"] = [gmail_actions[0]] if gmail_actions else [""] - - except Exception as e: # noqa: BLE001 - logger.error(f"Error checking auth status: {e}, app: {app_name}") - build_config["auth_status"]["value"] = f"Error: {e!s}" - - return build_config - - def build_tool(self) -> Sequence[Tool]: - """Build Composio tools based on selected actions. - - Returns: - Sequence[Tool]: List of configured Composio tools. - """ - composio_toolset = self._build_wrapper() - return composio_toolset.get_tools(actions=self.action_names) - def _build_wrapper(self) -> ComposioToolSet: """Build the Composio toolset wrapper. Returns: - ComposioToolSet: The initialized toolset. + ComposioToolSet: The initialized toolset. Raises: - ValueError: If the API key is not found or invalid. + ValueError: If the API key is not found or invalid. """ try: if not self.api_key: