""" Composio exceptions. """ import difflib import typing as t ENV_COMPOSIO_API_KEY = "COMPOSIO_API_KEY" class ComposioError(Exception): """Base composio SDK error.""" def __init__( self, message: str, *args: t.Any, delegate: bool = False, ) -> None: """ Initialize Composio SDK error. :param message: Error message :param delegate: Whether to delegate the error message to the log collection server or not """ super().__init__(message, *args) self.message = message self.delegate = delegate class NotFoundError(ComposioError): pass class HTTPError(ComposioError): """ Exception class for HTTP API errors. """ def __init__( self, message: str, status_code: int, *args: t.Any, delegate: bool = False, ) -> None: """ Initialize HTTPError class. :param message: Content from the API response :param status_code: HTTP response status code :param delegate: Whether to delegate the error message to the log collection server or not """ super().__init__(message, *args, delegate=delegate) self.status_code = status_code class ComposioClientError(ComposioError): """ Exception class for Composio client errors. """ class SDKError(ComposioError): pass class ProcessorError(SDKError): pass class EnumError(ComposioError): pass class ValidationError(ComposioError): pass class JSONSchemaRefResolutionError(ValidationError): """Raised when an internal JSON Schema ``$ref`` cannot be inlined. Covers malformed pointers, missing ``$defs``/``definitions`` targets (strict mode only), and ``$ref`` chains or node nesting that exceed the safety caps in :func:`composio.utils.json_schema.dereference_json_schema`. """ def __init__( self, message: str, *args: t.Any, meta: t.Optional[t.Dict[str, t.Any]] = None, delegate: bool = False, ) -> None: super().__init__(message, *args, delegate=delegate) self.meta = meta or {} class ToolkitError(ComposioError): pass class EntityIDError(ComposioError): pass class PluginError(ComposioError): pass class InvalidParams(ComposioError): pass class FileError(ComposioError): pass class ComposioSDKTimeoutError(ComposioError, TimeoutError): pass class SDKFileNotFoundError(ComposioError, FileNotFoundError): pass class LockFileError(ComposioError): pass class VersionError(ComposioError): pass class InvalidLockFile(LockFileError): pass class InvalidVersionString(EnumError): pass class VersionSelectionError(LockFileError, VersionError): def __init__( self, action: str, requested: str, locked: str, delegate: bool = False, ) -> None: self.action = action self.requested = requested self.locked = locked super().__init__( message=( f"Error selecting version for action: {action!r}, " f"requested: {requested!r}, locked: {locked!r}" ), delegate=delegate, ) class InvalidEnum(EnumError): pass class EnumStringNotFound(EnumError): """Raise when user provides invalid enum string.""" def __init__(self, value: str, enum: str, possible_values: t.List[str]) -> None: error_message = f"Invalid value `{value}` for enum class `{enum}`" matches = difflib.get_close_matches(value, possible_values, n=1) if matches: (match,) = matches error_message += f". Did you mean {match!r}?" super().__init__(message=error_message) class EnumMetadataNotFound(EnumError): pass class ErrorUploadingFile(FileError): pass class SensitiveFilePathBlockedError(FileError): """Raised when a local file path is refused before upload (sensitive directory or credential-like name).""" class FileUploadPathNotAllowedError(FileError): """ Raised when automatic file upload during tool execution is attempted from a path outside the configured ``file_upload_dirs`` allowlist. Only fires for auto-upload (enabled via ``dangerously_allow_auto_upload_download_files=True``). Manual upload APIs are not subject to this check. """ class FileUploadAbortedError(FileError): """Raised when a ``before_file_upload`` hook returns ``False``.""" class ErrorDownloadingFile(FileError): pass class RemoteFileDownloadError(FileError): """Raised when fetching a remote file from a tool router session mount fails. Includes HTTP status, URL, and file path context for debugging. """ def __init__( self, message: str = "Failed to download remote file", *, status_code: t.Optional[int] = None, status_text: t.Optional[str] = None, download_url: t.Optional[str] = None, mount_relative_path: t.Optional[str] = None, filename: t.Optional[str] = None, **kwargs: t.Any, ) -> None: super().__init__(message, **kwargs) self.status_code = status_code self.status_text = status_text self.download_url = download_url self.mount_relative_path = mount_relative_path self.filename = filename class ResponseTooLargeError(FileError): """Raised when a response exceeds the maximum allowed size.""" pass class TriggerError(ToolkitError): pass class WebhookSignatureVerificationError(TriggerError): """Raised when webhook signature verification fails.""" pass class WebhookPayloadError(TriggerError): """Raised when webhook payload is invalid.""" pass class TriggerTypeNotFound(TriggerError, NotFoundError): """Raised when a trigger type cannot be found for the given slug. Mirrors the TypeScript SDK's ``ComposioTriggerTypeNotFoundError``. """ pass class ActionError(ToolkitError): pass class TriggerSubscriptionError(TriggerError, ComposioClientError): pass class InvalidTriggerFilters(TriggerSubscriptionError): pass class ApiKeyError(ComposioClientError): pass class ApiKeyNotProvidedError(ApiKeyError, NotFoundError): """Raise when API key is required but not provided.""" def __init__(self) -> None: super().__init__( message=( "API Key not provided, either provide API key " f"or export it as `{ENV_COMPOSIO_API_KEY}` " "or run `composio login`" ), ) class ResourceError(ComposioClientError): pass class NoItemsFound(ResourceError): """ Exception class for empty collection values. """ class ErrorFetchingResource(ResourceError): pass class SchemaError(ToolkitError): pass class InvalidSchemaError(SchemaError, TypeError): pass class InvalidEntityIdError(EntityIDError, ValueError): pass class IntegrationError(ResourceError): pass class ConnectedAccountError(ResourceError): pass class ConnectedAccountNotFoundError(NotFoundError, ConnectedAccountError): pass class InvalidConnectedAccount(ValidationError, ConnectedAccountError): pass class ComposioMultipleConnectedAccountsError(ConnectedAccountError): """Raised when multiple connected accounts are found for a user and auth config.""" pass class ComposioLegacyConnectedAccountsEndpointRetiredError(ConnectedAccountError): """Raised by ``composio.connected_accounts.initiate()`` when the legacy ``POST /api/v3/connected_accounts`` endpoint rejects a Composio-managed OAuth (OAuth1, OAuth2, DCR_OAUTH) auth-config request. Cutover dates: 2026-05-08 (new orgs), 2026-07-03 (all remaining orgs). Migrate to ``composio.connected_accounts.link()`` — same return shape, works for every redirectable scheme regardless of whether the auth config is Composio-managed or custom. See: https://docs.composio.dev/docs/changelog/2026/04/24 """ pass class ComposioAclOnlyForSharedError(ConnectedAccountError): """Raised when ACL fields (``allow_all_users``, ``allowed_user_ids``, ``not_allowed_user_ids``) are sent on a ``PRIVATE`` connection. ACL is only meaningful for ``SHARED`` connections. Fix: use ``account_type='SHARED'``, or omit the ACL fields when creating/updating a PRIVATE connection. """ pass class ComposioSharedAccessDeniedError(ConnectedAccountError): """Raised when a tool execution attempts to use a SHARED connected account but the requesting ``user_id`` is not allowed by the connection's ACL. Surfaces when a SHARED connection is reached directly (e.g. via ``composio.tools.execute(slug, connected_account_id=...)``) without going through a tool-router session. Fix: ask the connection's creator to grant access via ``composio.experimental.update_acl()`` — set ``allow_all_users``, add the ``user_id`` to ``allowed_user_ids``, or remove it from ``not_allowed_user_ids``. """ pass class ComposioSharedConnectionNotAccessibleError(ConnectedAccountError): """Raised by ``tool_router.session.create()`` / ``session.patch()`` when the session's ``user_id`` cannot use a pinned SHARED connection. Raised at session-create time so the session never enters a state that fails mid-execution. Fix: grant the session user access via ``composio.experimental.update_acl()`` on the pinned connection, or pin a different connection the user can use. """ pass class ErrorProcessingToolExecutionRequest(PluginError): pass class DescopeAuthError(ComposioError): pass class DescopeConfigError(ComposioError): pass class InvalidExecuteFunctionError(ComposioError): pass class ToolNotFoundError(ComposioError): pass class InvalidModifier(ComposioError): pass class ExecuteToolFnNotSetError(ComposioError): pass class ToolVersionRequiredError(ComposioError): """Raised when toolkit version is not specified for manual tool execution.""" def __init__(self) -> None: super().__init__( message=( "Toolkit version not specified. For manual execution of the tool " "please pass a specific toolkit version.\n\n" "Possible fixes:\n" "1. Pass the toolkit version as a parameter to the execute function " '("latest" is not supported in manual execution)\n' "2. Set the toolkit versions in the Composio config " "(toolkit_versions={'': ''})\n" "3. Set the toolkit version in the environment variable " "(COMPOSIO_TOOLKIT_VERSION_)\n" "4. Set dangerously_skip_version_check to True " "(this might cause unexpected behavior when new versions of the tools are released)" ), ) class UsageError(ComposioError): pass