```diff
--- test_files/366-original.txt	2025-03-07 19:06:50
+++ test_files/366-modified.txt	2025-03-07 19:06:50
@@ -3,6 +3,7 @@
 import hashlib
 import inspect
 import json
+import textwrap
 import typing as t
 from abc import abstractmethod
 from pathlib import Path
@@ -13,7 +14,10 @@
 from pydantic import BaseModel, Field
 
 from composio.client.enums import Action as ActionEnum
+from composio.client.enums.base import DEPRECATED_MARKER
+from composio.exceptions import ComposioSDKError
 from composio.utils.logging import WithLogger
+from composio.utils.pydantic import parse_pydantic_error
 
 
 GroupID = t.Literal["runtime", "local", "api"]
@@ -23,6 +27,7 @@
 Loadable = t.TypeVar("Loadable")
 ToolRegistry = t.Dict[GroupID, t.Dict[str, "Tool"]]
 ActionsRegistry = t.Dict[GroupID, t.Dict[str, "Action"]]
+# TODO: create a Trigger type for this
 TriggersRegistry = t.Dict[GroupID, t.Dict[str, t.Any]]
 
 tool_registry: ToolRegistry = {"runtime": {}, "local": {}, "api": {}}
@@ -36,6 +41,7 @@
             jsonref.replace_refs(
                 obj=data,
                 lazy_load=False,
+                merge_props=True,
             ),
             indent=2,
         )
@@ -57,13 +63,17 @@
     )
 
 
+class InvalidClassDefinition(ComposioSDKError):
+    """Raise when a class is not defined properly."""
+
+
 class ExecuteResponse(BaseModel):
     """Execute action response."""
 
 
 class _Attributes:
     name: str
-    """Name represenation."""
+    """Name representation."""
 
     enum: str
     """Enum key."""
@@ -74,7 +84,10 @@
     description: str
     """Description string."""
 
+    logo: str
+    """URL for the resource logo."""
 
+
 class _Request(t.Generic[ModelType]):
     """Request util."""
 
@@ -106,24 +119,19 @@
                 del prop["type"]  # Remove original type to avoid conflict in oneOf
                 continue
 
-            if (
-                "allOf" in prop
-                and len(prop["allOf"]) == 1
-                and "enum" in prop["allOf"][0]
-            ):
+            if "allOf" in prop and len(prop["allOf"]) == 1:
                 (schema,) = prop.pop("allOf")
                 prop.update(schema)
-                prop[
-                    "description"
-                ] += f" Note: choose value only from following options - {prop['enum']}"
+                if "enum" in schema:
+                    prop[
+                        "description"
+                    ] += f" Note: choose value only from following options - {prop['enum']}"
 
-            if (
-                "allOf" in prop
-                and len(prop["allOf"]) == 1
-                and prop["allOf"][0]["title"] == "FileType"
-            ):
-                (schema,) = prop.pop("allOf")
-                prop.update(schema)
+            if "anyOf" in prop:
+                typedef, *_ = [
+                    td for td in prop["anyOf"] if td.get("type", "null") != "null"
+                ]
+                prop["type"] = typedef["type"]
 
         request["properties"] = properties
         return request
@@ -133,19 +141,7 @@
         try:
             return self.model(**request)
         except pydantic.ValidationError as e:
-            message = "Invalid request data provided"
-            missing = []
-            others = [""]
-            for error in e.errors():
-                param = ".".join(map(str, error["loc"]))
-                if error["type"] == "missing":
-                    missing.append(param)
-                    continue
-                others.append(error["msg"] + f" on parameter `{param}`")
-            if len(missing) > 0:
-                message += f"\n- Following fields are missing: {set(missing)}"
-            message += "\n- ".join(others)
-            raise ValueError(message) from e
+            raise ValueError(parse_pydantic_error(e)) from e
 
 
 class _Response(t.Generic[ModelType]):
@@ -158,14 +154,32 @@
 
     @classmethod
     def wrap(cls, model: t.Type[ModelType]) -> t.Type[BaseModel]:
-        class wrapper(model):  # type: ignore
+        if "data" not in model.__annotations__:
+
+            class wrapper(BaseModel):  # type: ignore
+                data: model = Field(  # type: ignore
+                    ...,
+                    description="Data from the action execution",
+                )
+                successful: bool = Field(
+                    ...,
+                    description="Whether or not the action execution was successful or not",
+                )
+                error: t.Optional[str] = Field(
+                    None,
+                    description="Error if any occurred during the execution of the action",
+                )
+
+            return t.cast(t.Type[BaseModel], wrapper)
+
+        class wrapper(model):  # type: ignore # pylint: disable=function-redefined
             successful: bool = Field(
                 ...,
                 description="Whether or not the action execution was successful or not",
             )
             error: t.Optional[str] = Field(
                 None,
-                description="Error if any occured during the execution of the action",
+                description="Error if any occurred during the execution of the action",
             )
 
         return t.cast(t.Type[BaseModel], wrapper)
@@ -173,20 +187,57 @@
     def schema(self) -> t.Dict:
         """Build request schema."""
         schema = self.wrapper.model_json_schema(by_alias=True)
-        schema["title"] = self.model.__name__
+        schema = remove_json_ref(schema)
+        if "$defs" in schema:
+            del schema["$defs"]
+
+        properties = schema.get("properties", {})
+        for prop in properties.values():
+            if prop.get("file_readable", False):
+                prop["oneOf"] = [
+                    {
+                        "type": prop.get("type"),
+                        "description": prop.get("description", ""),
+                    },
+                    {
+                        "type": "string",
+                        "format": "file-path",
+                        "description": f"File path to {prop.get('description', '')}",
+                    },
+                ]
+                del prop["type"]  # Remove original type to avoid conflict in oneOf
+                continue
+
+            if "allOf" in prop and len(prop["allOf"]) == 1:
+                (schema,) = prop.pop("allOf")
+                prop.update(schema)
+                if "enum" in schema:
+                    prop[
+                        "description"
+                    ] += f" Note: choose value only from following options - {prop['enum']}"
+
+        schema["properties"] = properties
+        schema["title"] = f"{self.model.__name__}Wrapper"
         return remove_json_ref(schema)
 
 
 class ActionBuilder:
     @staticmethod
-    def set_generics(name: str, obj: t.Type["Action"]) -> None:
+    def get_generics(obj: t.Type["Action"]) -> t.Tuple[t.Any, t.Any]:
+        for base in getattr(obj, "__orig_bases__"):
+            args = t.get_args(base)
+            if len(args) == 2:
+                return args  # type: ignore
+        raise ValueError("No type generics found")
+
+    @classmethod
+    def set_generics(cls, name: str, obj: t.Type["Action"]) -> None:
         try:
-            (generic,) = getattr(obj, "__orig_bases__")
-            request, response = t.get_args(generic)
+            request, response = cls.get_generics(obj=obj)
             if request == ActionRequest or response == ActionResponse:
                 raise ValueError(f"Invalid type generics, ({request}, {response})")
         except ValueError as e:
-            raise ValueError(
+            raise InvalidClassDefinition(
                 "Invalid action class definition, please define your class "
                 "using request and response type generics; "
                 f"class {name}(Action[RequestModel, ResponseModel])"
@@ -198,10 +249,10 @@
     @staticmethod
     def validate(name: str, obj: t.Type["Action"]) -> None:
         if getattr(getattr(obj, "execute"), "__isabstractmethod__", False):
-            raise RuntimeError(f"Please implement {name}.execute")
+            raise InvalidClassDefinition(f"Please implement {name}.execute")
 
-    @staticmethod
-    def set_metadata(obj: t.Type["Action"]) -> None:
+    @classmethod
+    def set_metadata(cls, obj: t.Type["Action"]) -> None:
         setattr(obj, "file", getattr(obj, "file", Path(inspect.getfile(obj))))
         setattr(obj, "name", getattr(obj, "name", inflection.underscore(obj.__name__)))
         setattr(
@@ -212,15 +263,38 @@
         setattr(
             obj,
             "display_name",
-            getattr(obj, "display_name", inflection.humanize(obj.__name__)),
+            getattr(
+                obj,
+                "display_name",
+                inflection.humanize(inflection.underscore(obj.__name__)),
+            ),
         )
         setattr(
             obj,
             "description",
-            (obj.__doc__ or obj.display_name).lstrip().rstrip(),
+            cls._get_description(obj),
         )
+        description, *_ = obj.description.split(DEPRECATED_MARKER, maxsplit=1)
+        if len(description) > 1024:
+            raise InvalidClassDefinition(
+                f"Description for action `{obj.__name__}` contains more than 1024 characters"
+            )
 
+    @staticmethod
+    def _get_description(obj) -> str:
+        description = t.cast(
+            str,
+            (
+                (obj.__doc__ if obj.__doc__ else obj.display_name)
+                .replace("\n    ", " ")
+                .strip()
+            ),
+        )
+        description = " ".join(description.split())
+        description, separator, enum = description.partition(DEPRECATED_MARKER)
+        return inflection.humanize(description) + separator + enum
 
+
 class ActionMeta(type):
     """Action metaclass."""
 
@@ -282,21 +356,16 @@
     @classmethod
     def _generate_schema(cls) -> None:
         """Generate action schema."""
-        description = (
-            cls.__doc__.lstrip().rstrip()
-            if cls.__doc__
-            else inflection.titleize(cls.display_name)
-        )
         cls._schema = {
             "name": cls.name,
             "enum": cls.enum,
             "appName": cls.tool,
             "appId": generate_app_id(cls.tool),
             "tags": cls.tags(),
-            "displayName": cls.display_name,
-            "description": description,
-            "parameters": cls.request.schema(),
             "response": cls.response.schema(),
+            "parameters": cls.request.schema(),
+            "displayName": cls.display_name,
+            "description": cls.description,
         }
 
     @classmethod
@@ -320,36 +389,49 @@
     def validate(obj: t.Type["Tool"], name: str, methods: t.Tuple[str, ...]) -> None:
         for method in methods:
             if getattr(getattr(obj, method), "__isabstractmethod__", False):
-                raise RuntimeError(f"Please implement {name}.{method}")
+                raise InvalidClassDefinition(f"Please implement {name}.{method}")
 
             if not inspect.ismethod(getattr(obj, method)):
-                raise RuntimeError(f"Please implement {name}.{method} as class method")
+                raise InvalidClassDefinition(
+                    f"Please implement {name}.{method} as class method"
+                )
 
-    @staticmethod
-    def set_metadata(obj: t.Type["Tool"]) -> None:
-        setattr(obj, "description", (obj.__doc__ or "").lstrip().rstrip())
+    @classmethod
+    def set_metadata(cls, obj: t.Type["Tool"]) -> None:
         setattr(obj, "file", Path(inspect.getfile(obj)))
+        setattr(obj, "gid", getattr(obj, "gid", "local"))
         setattr(obj, "name", getattr(obj, "name", inflection.underscore(obj.__name__)))
         setattr(obj, "enum", getattr(obj, "enum", obj.name).upper())
         setattr(
             obj,
             "display_name",
-            getattr(obj, "display_name", inflection.humanize(obj.__name__)),
+            getattr(
+                obj,
+                "display_name",
+                inflection.humanize(inflection.underscore(obj.__name__)),
+            ),
         )
+        setattr(obj, "description", cls._get_description(obj=obj))
         setattr(obj, "_actions", getattr(obj, "_actions", {}))
         setattr(obj, "_triggers", getattr(obj, "_triggers", {}))
 
     @staticmethod
-    def setup_children(obj: t.Type["Tool"]) -> None:
+    def setup_children(obj: t.Type["Tool"], no_auth: bool = False) -> None:
         if obj.gid not in action_registry:
             action_registry[obj.gid] = {}
 
         for action in obj.actions():
             action.tool = obj.name
             action.enum = f"{obj.enum}_{action.name.upper()}"
+            action.no_auth = no_auth
+            if obj.requires is not None:
+                action.requires = list(set(obj.requires + (action.requires or [])))
             obj._actions[action.enum] = action  # pylint: disable=protected-access
             action_registry[obj.gid][action.enum] = action  # type: ignore
 
+            if hasattr(obj, "logo"):
+                setattr(action, "logo", getattr(obj, "logo"))
+
         if not hasattr(obj, "triggers"):
             return
 
@@ -362,7 +444,22 @@
             obj._triggers[trigger.enum] = trigger  # type: ignore  # pylint: disable=protected-access
             trigger_registry[obj.gid][trigger.enum] = trigger  # type: ignore
 
+            if hasattr(obj, "logo"):
+                setattr(trigger, "logo", getattr(obj, "logo"))
 
+    @staticmethod
+    def _get_description(obj) -> str:
+        return " ".join(
+            line
+            for line in textwrap.dedent(
+                (obj.__doc__ if obj.__doc__ else obj.display_name)
+            )
+            .strip()
+            .splitlines()
+            if line
+        )
+
+
 class Tool(WithLogger, _Attributes):
     """Tool abstraction."""
 
@@ -375,6 +472,9 @@
     name: str
     """Tool name."""
 
+    requires: t.Optional[t.List[str]] = None
+    """List of dependencies required for running this tool."""
+
     _schema: t.Optional[t.Dict] = None
     """Schema for the app."""
 
@@ -437,7 +537,7 @@
         :param params: Execution parameters.
         :param metadata: A dictionary containing metadata for action.
         """
-        raise NotImplementedError()
+        raise NotImplementedError
 
     @classmethod
     def register(cls: t.Type["Tool"]) -> None:
```
