feat: add composio toolset (#3034)

* feat: add composio toolset

* feat: v1

* feat: format code

* feat: add support for multi tools

* feat: make methods private

* [autofix.ci] apply automated fixes

* feat: use logger

* refactor(ComposioAPI.py): reorganize import statement

* refactor: update typing import in langchain_utilities/model.py

* refactor: update typing import in ComposioAPI.py

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Himanshu Dixit 2024-07-31 02:44:32 +05:30 committed by GitHub
commit 818a17d0db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 559 additions and 4 deletions

231
poetry.lock generated
View file

@ -1,4 +1,16 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "aenum"
version = "3.1.15"
description = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants"
optional = false
python-versions = "*"
files = [
{file = "aenum-3.1.15-py2-none-any.whl", hash = "sha256:27b1710b9d084de6e2e695dab78fe9f269de924b51ae2850170ee7e1ca6288a5"},
{file = "aenum-3.1.15-py3-none-any.whl", hash = "sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288"},
{file = "aenum-3.1.15.tar.gz", hash = "sha256:8cbd76cd18c4f870ff39b24284d3ea028fbe8731a58df3aa581e434c575b9559"},
]
[[package]]
name = "aiofiles"
@ -1306,6 +1318,58 @@ traitlets = ">=4"
[package.extras]
test = ["pytest"]
[[package]]
name = "composio-core"
version = "0.3.28"
description = "Core package to act as a bridge between composio platform and other services."
optional = false
python-versions = "<4,>=3.9"
files = [
{file = "composio_core-0.3.28-py3-none-any.whl", hash = "sha256:2c863fbf60096df4593a2bfa12f776318a2c6c852c12555e45e8d1c9977f36a9"},
{file = "composio_core-0.3.28.tar.gz", hash = "sha256:31453edd3818518144709ad456be05cf19718c6e7d703c6bdb277e5491a1514a"},
]
[package.dependencies]
aiohttp = "*"
click = "*"
docker = ">=7.1.0"
e2b-code-interpreter = "*"
fastapi = "*"
gql = "*"
importlib-metadata = ">=4.8.1"
inflection = ">=0.5.1"
jsonref = ">=1.1.0"
jsonschema = ">=4.21.1,<5"
paramiko = "*"
pydantic = ">=2.6.4,<3"
pyperclip = ">=1.8.2,<2"
pysher = "1.0.8"
requests = ">=2.31.0,<3"
requests-toolbelt = "*"
rich = ">=13.7.1,<14"
sentry-sdk = ">=2.0.0"
[package.extras]
all = ["diskcache", "flake8", "networkx", "pathspec", "pygments", "ruff", "transformers", "tree-sitter (==0.21.3)", "tree-sitter-languages"]
[[package]]
name = "composio-langchain"
version = "0.3.28"
description = "Use Composio to get an array of tools with your LangChain agent."
optional = false
python-versions = "<4,>=3.9"
files = [
{file = "composio_langchain-0.3.28-py3-none-any.whl", hash = "sha256:b75b52e2ffd02eef0f63f714719c96eaaddd7ba63ab6dfe1a213add8c3d94dab"},
{file = "composio_langchain-0.3.28.tar.gz", hash = "sha256:638b8b17140b5af96be848f1e701f5b1fcae6a47dac700c87208365f7445f315"},
]
[package.dependencies]
composio-core = "0.3.28"
langchain = ">=0.1.0"
langchain-openai = ">=0.0.2.post1"
langchainhub = ">=0.1.15"
pydantic = ">=2.6.4"
[[package]]
name = "configargparse"
version = "1.7"
@ -1949,6 +2013,44 @@ files = [
{file = "duckdb-1.0.0.tar.gz", hash = "sha256:a2a059b77bc7d5b76ae9d88e267372deff19c291048d59450c431e166233d453"},
]
[[package]]
name = "e2b"
version = "0.17.1"
description = "E2B SDK that give agents cloud environments"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "e2b-0.17.1-py3-none-any.whl", hash = "sha256:c0698fd03b639f4dd88eed167a98af4d450668c0ae9805122a98f62f36f2491f"},
{file = "e2b-0.17.1.tar.gz", hash = "sha256:9e69a059cb73334bac7db189287552af9321fb3ac8ced52557907e10c4310733"},
]
[package.dependencies]
aenum = ">=3.1.11"
aiohttp = ">=3.8.4"
jsonrpcclient = ">=4.0.3"
pydantic = "*"
python-dateutil = ">=2.8.2"
requests = ">=2.31.0"
typing-extensions = ">=4.8.0"
urllib3 = ">=1.25.3"
websockets = ">=11.0.3"
[[package]]
name = "e2b-code-interpreter"
version = "0.0.10"
description = "E2B Code Interpreter - Stateful code execution"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "e2b_code_interpreter-0.0.10-py3-none-any.whl", hash = "sha256:85700fad734334678a11e6b8cfea9dfd5af7f2f16b8f9a5950cf06b1877c02da"},
{file = "e2b_code_interpreter-0.0.10.tar.gz", hash = "sha256:2882197b819e657c5b03083b2330c8e06117e7a584ca93e6d1acded9da517622"},
]
[package.dependencies]
e2b = ">=0.17.1"
pydantic = "*"
websocket-client = ">=1.7.0,<2.0.0"
[[package]]
name = "ecdsa"
version = "0.19.0"
@ -3274,6 +3376,45 @@ cachetools = "*"
numpy = "*"
requests = "*"
[[package]]
name = "gql"
version = "3.5.0"
description = "GraphQL client for Python"
optional = false
python-versions = "*"
files = [
{file = "gql-3.5.0-py2.py3-none-any.whl", hash = "sha256:70dda5694a5b194a8441f077aa5fb70cc94e4ec08016117523f013680901ecb7"},
{file = "gql-3.5.0.tar.gz", hash = "sha256:ccb9c5db543682b28f577069950488218ed65d4ac70bb03b6929aaadaf636de9"},
]
[package.dependencies]
anyio = ">=3.0,<5"
backoff = ">=1.11.1,<3.0"
graphql-core = ">=3.2,<3.3"
yarl = ">=1.6,<2.0"
[package.extras]
aiohttp = ["aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)"]
all = ["aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "botocore (>=1.21,<2)", "httpx (>=0.23.1,<1)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "websockets (>=10,<12)"]
botocore = ["botocore (>=1.21,<2)"]
dev = ["aiofiles", "aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "black (==22.3.0)", "botocore (>=1.21,<2)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "httpx (>=0.23.1,<1)", "isort (==4.3.21)", "mock (==4.0.2)", "mypy (==0.910)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "sphinx (>=5.3.0,<6)", "sphinx-argparse (==0.2.5)", "sphinx-rtd-theme (>=0.4,<1)", "types-aiofiles", "types-mock", "types-requests", "vcrpy (==4.4.0)", "websockets (>=10,<12)"]
httpx = ["httpx (>=0.23.1,<1)"]
requests = ["requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)"]
test = ["aiofiles", "aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "botocore (>=1.21,<2)", "httpx (>=0.23.1,<1)", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "vcrpy (==4.4.0)", "websockets (>=10,<12)"]
test-no-transport = ["aiofiles", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "vcrpy (==4.4.0)"]
websockets = ["websockets (>=10,<12)"]
[[package]]
name = "graphql-core"
version = "3.2.3"
description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL."
optional = false
python-versions = ">=3.6,<4"
files = [
{file = "graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"},
{file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"},
]
[[package]]
name = "greenlet"
version = "3.0.3"
@ -3972,6 +4113,17 @@ files = [
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"]
[[package]]
name = "inflection"
version = "0.5.1"
description = "A port of Ruby on Rails inflector to Python"
optional = false
python-versions = ">=3.5"
files = [
{file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"},
{file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
]
[[package]]
name = "iniconfig"
version = "2.0.0"
@ -4383,6 +4535,19 @@ files = [
{file = "jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552"},
]
[[package]]
name = "jsonrpcclient"
version = "4.0.3"
description = "Send JSON-RPC requests"
optional = false
python-versions = ">=3.6"
files = [
{file = "jsonrpcclient-4.0.3-py3-none-any.whl", hash = "sha256:3cbb9e27e1be29821becf135ea183144a836215422727e1ffe5056a49a670f0d"},
]
[package.extras]
qa = ["pytest", "pytest-cov", "tox"]
[[package]]
name = "jsonschema"
version = "4.23.0"
@ -6308,6 +6473,7 @@ description = "Nvidia JIT LTO Library"
optional = true
python-versions = ">=3"
files = [
{file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-manylinux2014_aarch64.whl", hash = "sha256:98103729cc5226e13ca319a10bbf9433bbbd44ef64fe72f45f067cacc14b8d27"},
{file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f9b37bc5c8cf7509665cb6ada5aaa0ce65618f2332b7d3e78e9790511f111212"},
{file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-win_amd64.whl", hash = "sha256:e782564d705ff0bf61ac3e1bf730166da66dd2fe9012f111ede5fc49b64ae697"},
]
@ -6844,6 +7010,27 @@ files = [
[package.extras]
dev = ["jinja2"]
[[package]]
name = "paramiko"
version = "3.4.0"
description = "SSH2 protocol library"
optional = false
python-versions = ">=3.6"
files = [
{file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"},
{file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"},
]
[package.dependencies]
bcrypt = ">=3.2"
cryptography = ">=3.3"
pynacl = ">=1.5"
[package.extras]
all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"]
gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"]
invoke = ["invoke (>=2.0)"]
[[package]]
name = "parso"
version = "0.8.4"
@ -7953,6 +8140,32 @@ snappy = ["python-snappy"]
test = ["pytest (>=7)"]
zstd = ["zstandard"]
[[package]]
name = "pynacl"
version = "1.5.0"
description = "Python binding to the Networking and Cryptography (NaCl) library"
optional = false
python-versions = ">=3.6"
files = [
{file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
{file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
{file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
{file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
{file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
{file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
]
[package.dependencies]
cffi = ">=1.4.1"
[package.extras]
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]]
name = "pyparsing"
version = "3.1.2"
@ -8058,6 +8271,20 @@ files = [
{file = "pysbd-0.3.4-py3-none-any.whl", hash = "sha256:cd838939b7b0b185fcf86b0baf6636667dfb6e474743beeff878e9f42e022953"},
]
[[package]]
name = "pysher"
version = "1.0.8"
description = "Pusher websocket client for python, based on Erik Kulyk's PythonPusherClient"
optional = false
python-versions = "*"
files = [
{file = "Pysher-1.0.8.tar.gz", hash = "sha256:7849c56032b208e49df67d7bd8d49029a69042ab0bb45b2ed59fa08f11ac5988"},
]
[package.dependencies]
requests = ">=2.26.0"
websocket-client = "!=0.49"
[[package]]
name = "pysocks"
version = "1.7.1"
@ -11768,4 +11995,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.13"
content-hash = "d5e5f7e03a57022c0548ff9042b68e43d23a923eafb75384e8132af1113e3d9d"
content-hash = "3d81751b63f97649e1b46a0d626c055303df6ab3bfce2e6b49941400aad08793"

View file

@ -105,6 +105,7 @@ yfinance = "^0.2.40"
langchain-google-community = "1.0.7"
wolframalpha = "^5.1.3"
astra-assistants = "^2.0.15"
composio-langchain = "^0.3.28"
[tool.poetry.group.dev.dependencies]

View file

@ -1,5 +1,5 @@
from abc import abstractmethod
from typing import Union
from typing import Sequence, Union
from langflow.custom import Component
from langflow.field_typing import Tool
@ -31,7 +31,7 @@ class LCToolComponent(Component):
pass
@abstractmethod
def build_tool(self) -> Tool:
def build_tool(self) -> Tool | Sequence[Tool]:
"""
Build the tool.
"""

View file

@ -0,0 +1,172 @@
from typing import Any, Sequence
from composio_langchain import Action, App, ComposioToolSet # type: ignore
from langchain_core.tools import Tool
from loguru import logger
from langflow.base.langchain_utilities.model import LCToolComponent
from langflow.inputs import DropdownInput, MessageTextInput, MultiselectInput, SecretStrInput, StrInput
class ComposioAPIComponent(LCToolComponent):
display_name: str = "Composio Tools"
description: str = "Use Composio toolset to run actions with your agent"
name = "ComposioAPI"
icon = "Composio"
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,
refresh_button=True,
info="Refer to https://docs.composio.dev/introduction/foundations/howtos/get_api_key",
),
DropdownInput(
name="app_names",
display_name="App Name",
options=[app_name for app_name in App.__annotations__],
value="",
info="The app name to use. Please refresh after selecting app name",
refresh_button=True,
),
MultiselectInput(
name="action_names",
display_name="Actions to use",
required=False,
options=[],
value=[],
info="The actions to pass to agent to execute",
),
StrInput(
name="auth_status_config",
display_name="Auth status",
value="",
refresh_button=True,
info="Open link or enter api key. Then refresh button",
),
]
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.
"""
toolset = self._build_wrapper()
entity = toolset.client.get_entity(id=self.entity_id)
try:
entity.get_connection(app=app)
return f"{app} CONNECTED"
except Exception:
return self._handle_authorization_failure(toolset, entity, app)
def _handle_authorization_failure(self, toolset: ComposioToolSet, entity: Any, app: str) -> str:
"""
Handles the authorization failure by attempting to process API key auth or initiate default connection.
Args:
toolset (ComposioToolSet): The toolset instance.
entity (Any): The entity instance.
app (str): The app name.
Returns:
str: The result of the authorization failure message.
"""
try:
auth_schemes = toolset.client.apps.get(app).auth_schemes
if auth_schemes[0].auth_mode == "API_KEY":
return self._process_api_key_auth(entity, app)
else:
return self._initiate_default_connection(entity, app)
except Exception as exc:
logger.error(f"Authorization error: {str(exc)}")
return "Error"
def _process_api_key_auth(self, entity: Any, app: str) -> str:
"""
Processes the API key authentication.
Args:
entity (Any): The entity instance.
app (str): The app name.
Returns:
str: The status of the API key authentication.
"""
auth_status_config = self.auth_status_config
is_url = "http" in auth_status_config or "https" in auth_status_config
is_different_app = "CONNECTED" in auth_status_config and app not in auth_status_config
is_default_api_key_message = "API Key" in auth_status_config
if is_different_app or is_url or is_default_api_key_message:
return "Enter API Key"
else:
if not is_default_api_key_message:
entity.initiate_connection(
app_name=app,
auth_mode="API_KEY",
auth_config={"api_key": self.auth_status_config},
use_composio_auth=False,
force_new_integration=True,
)
return f"{app} CONNECTED"
else:
return "Enter API Key"
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 _get_connected_app_names_for_entity(self) -> list[str]:
toolset = self._build_wrapper()
connections = toolset.client.get_entity(id=self.entity_id).get_connections()
return list(set(connection.appUniqueId for connection in connections))
def _update_app_names_with_connected_status(self, build_config: dict) -> dict:
connected_app_names = self._get_connected_app_names_for_entity()
app_names = [
f"{app_name}_CONNECTED" for app_name in App.__annotations__ if app_name.lower() in connected_app_names
]
non_connected_app_names = [
app_name for app_name in App.__annotations__ if app_name.lower() not in connected_app_names
]
build_config["app_names"]["options"] = app_names + non_connected_app_names
build_config["app_names"]["value"] = app_names[0] if app_names else ""
return build_config
def _get_normalized_app_name(self) -> str:
return self.app_names.replace("_CONNECTED", "").replace("_connected", "")
def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:
if field_name == "api_key":
build_config = self._update_app_names_with_connected_status(build_config)
return build_config
if field_name in {"app_names", "auth_status_config"}:
build_config["auth_status_config"]["value"] = self._check_for_authorization(self._get_normalized_app_name())
all_action_names = [action_name for action_name in Action.__annotations__]
app_action_names = [
action_name
for action_name in all_action_names
if action_name.lower().startswith(self._get_normalized_app_name().lower() + "_")
]
build_config["action_names"]["options"] = app_action_names
build_config["action_names"]["value"] = [app_action_names[0]] if app_action_names else [""]
return build_config
def build_tool(self) -> Sequence[Tool]:
composio_toolset = self._build_wrapper()
composio_tools = composio_toolset.get_actions(actions=self.action_names)
return composio_tools
def _build_wrapper(self) -> ComposioToolSet:
return ComposioToolSet(api_key=self.api_key)

View file

@ -1,7 +1,9 @@
from .Metaphor import MetaphorToolkit
from .VectorStoreInfo import VectorStoreInfoComponent
from .ComposioAPI import ComposioAPIComponent
__all__ = [
"MetaphorToolkit",
"VectorStoreInfoComponent",
"ComposioAPIComponent",
]

View file

@ -0,0 +1,68 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="60"
height="63"
fill="none"
>
<path
fill="#9A4DFF"
d="M36.232 5H23.989C12.397 5 3 14.766 3 26.813v12.724C3 51.584 12.397 61.35 23.99 61.35h12.242c11.589 0 20.986-9.766 20.986-21.813V26.813C57.218 14.766 47.821 5 36.232 5"
/>
<path
fill="url(#a)"
d="M36.232 5H23.989C12.397 5 3 14.766 3 26.813v12.724C3 51.584 12.397 61.35 23.99 61.35h12.242c11.589 0 20.986-9.766 20.986-21.813V26.813C57.218 14.766 47.821 5 36.232 5"
/>
<path
stroke="url(#b)"
strokeWidth="1.44"
d="M37.98 5H22.238C11.615 5 3 13.953 3 24.996v16.358C3 52.397 11.612 61.35 22.238 61.35H37.98c10.623 0 19.238-8.953 19.238-19.996V24.996C57.218 13.953 48.606 5 37.98 5Z"
/>
<mask
id="c"
width="28"
height="28"
x="16"
y="19"
maskUnits="userSpaceOnUse"
style="maskType: luminance;"
>
<path fill="#fff" d="M16.994 19.542H43.23v27.266H16.994z" />
</mask>
<g mask="url(#c)">
<path
fill="#fff"
fill-rule="evenodd"
d="M32.952 20.899a.983.983 0 0 1 .444 1.095l-2.47 9.416h9.012a.9.9 0 0 1 .511.16.95.95 0 0 1 .344.423.99.99 0 0 1-.175 1.044L27.596 47.541a.899.899 0 0 1-1.138.19.95.95 0 0 1-.405-.474 1 1 0 0 1-.036-.633l2.472-9.412h-9.018a.9.9 0 0 1-.507-.159.95.95 0 0 1-.345-.425.99.99 0 0 1 .175-1.044l13.022-14.501a.899.899 0 0 1 1.132-.187"
clip-rule="evenodd"
/>
</g>
<defs>
<linearGradient
id="a"
x1="30.109"
x2="30.109"
y1="6.818"
y2="59.532"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#9A4DFF" />
<stop offset="0.31" stop-color="#8017F7" />
<stop offset="0.425" stop-color="#7A20E1" />
<stop offset="0.495" stop-color="#7A20E1" />
<stop offset="0.665" stop-color="#7C16F8" />
<stop offset="1" stop-color="#8222FF" />
</linearGradient>
<linearGradient
id="b"
x1="30.109"
x2="30.109"
y1="6.817"
y2="59.533"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#6F00FF" stop-opacity="0.18" />
<stop offset="1" stop-color="#600ED1" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,74 @@
const Icon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={32}
height={32}
viewBox="0 0 60 63"
fill="none"
{...props}
>
<path
fill="#9A4DFF"
d="M36.232 5H23.989C12.397 5 3 14.766 3 26.813v12.724C3 51.584 12.397 61.35 23.99 61.35h12.242c11.589 0 20.986-9.766 20.986-21.813V26.813C57.218 14.766 47.821 5 36.232 5"
/>
<path
fill="url(#a)"
d="M36.232 5H23.989C12.397 5 3 14.766 3 26.813v12.724C3 51.584 12.397 61.35 23.99 61.35h12.242c11.589 0 20.986-9.766 20.986-21.813V26.813C57.218 14.766 47.821 5 36.232 5"
/>
<path
stroke="url(#b)"
strokeWidth={1.44}
d="M37.98 5H22.238C11.615 5 3 13.953 3 24.996v16.358C3 52.397 11.612 61.35 22.238 61.35H37.98c10.623 0 19.238-8.953 19.238-19.996V24.996C57.218 13.953 48.606 5 37.98 5Z"
/>
<mask
id="c"
width={28}
height={28}
x={16}
y={19}
maskUnits="userSpaceOnUse"
style={{
maskType: "luminance",
}}
>
<path fill="#fff" d="M16.994 19.542H43.23v27.266H16.994z" />
</mask>
<g mask="url(#c)">
<path
fill="#fff"
fillRule="evenodd"
d="M32.952 20.899a.983.983 0 0 1 .444 1.095l-2.47 9.416h9.012a.9.9 0 0 1 .511.16.95.95 0 0 1 .344.423.99.99 0 0 1-.175 1.044L27.596 47.541a.899.899 0 0 1-1.138.19.95.95 0 0 1-.405-.474 1 1 0 0 1-.036-.633l2.472-9.412h-9.018a.9.9 0 0 1-.507-.159.95.95 0 0 1-.345-.425.99.99 0 0 1 .175-1.044l13.022-14.501a.899.899 0 0 1 1.132-.187"
clipRule="evenodd"
/>
</g>
<defs>
<linearGradient
id="a"
x1={30.109}
x2={30.109}
y1={6.818}
y2={59.532}
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#9A4DFF" />
<stop offset={0.31} stopColor="#8017F7" />
<stop offset={0.425} stopColor="#7A20E1" />
<stop offset={0.495} stopColor="#7A20E1" />
<stop offset={0.665} stopColor="#7C16F8" />
<stop offset={1} stopColor="#8222FF" />
</linearGradient>
<linearGradient
id="b"
x1={30.109}
x2={30.109}
y1={6.817}
y2={59.533}
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#6F00FF" stopOpacity={0.18} />
<stop offset={1} stopColor="#600ED1" />
</linearGradient>
</defs>
</svg>
);
export default Icon;

View file

@ -0,0 +1,9 @@
import React, { forwardRef } from "react";
import ComposioIconSVG from "./composio";
export const ComposioIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <ComposioIconSVG ref={ref} {...props} />;
});

View file

@ -172,6 +172,7 @@ import { BotMessageSquareIcon } from "../icons/BotMessageSquare";
import { CassandraIcon } from "../icons/Cassandra";
import { ChromaIcon } from "../icons/ChromaIcon";
import { CohereIcon } from "../icons/Cohere";
import { ComposioIcon } from "../icons/Composio";
import { ConfluenceIcon } from "../icons/Confluence";
import { CouchbaseIcon } from "../icons/Couchbase";
import { CrewAiIcon } from "../icons/CrewAI";
@ -392,6 +393,7 @@ export const nodeIconsLucide: iconsType = {
HuggingFaceEmbeddings: HuggingFaceIcon,
IFixitLoader: IFixIcon,
CrewAI: CrewAiIcon,
Composio: ComposioIcon,
Meta: MetaIcon,
CheckCheck,
Midjorney: MidjourneyIcon,