Files

492 lines
12 KiB
Python
Raw Permalink Normal View History

2025-06-17 12:58:00 +05:30
"""
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
fix(py): resolve $ref/$defs so file flags behind a reference aren't dropped (#3653) ## Problem Tools whose parameter schemas express their file fields through a `$ref`/`$defs` indirection lose auto file handling. With `dangerously_allow_auto_upload_download_files=True`, a `file_downloadable` output behind a `$ref` is never downloaded (the raw `{name, mimetype, s3url}` object is passed through), and a `file_uploadable` input behind a `$ref` is never staged — silently, with no error. `GMAIL_GET_ATTACHMENT` is the canonical shape — the flag sits two `$ref` hops deep: ``` data → $ref → #/$defs/GetAttachmentResponse → file → $ref → #/$defs/FileDownloadable (file_downloadable: true) ``` **Root cause:** the hand-rolled walkers in `composio/core/models/_files.py` (`_has_file_property`, `_substitute_file_upload_value`, `_substitute_file_download_value`, …) recurse through `properties`/`anyOf`/`oneOf`/`allOf`/`items` but **never dereference `$ref`**, so a flagged node reachable only through a reference is invisible to them. Fixes https://github.com/ComposioHQ/composio/issues/3506 ## Approach Rather than teach each walker to dereference — which means threading a root schema through ~10 methods, re-deriving `$ref` handling in each, and risking unbounded recursion on cyclic schemas — this introduces a single **`dereference_json_schema`** utility and inlines references **once at the `FileHelper` boundary** (`process_file_uploadable_schema`, `substitute_file_uploads`, `substitute_file_downloads`). The walkers stay reference-agnostic. This mirrors the TypeScript SDK's `dereferenceJsonSchema` (the counterpart fix in #3566 for the TS twin of this bug, #3307) so both SDKs share one behavioral contract. Resolving once at the boundary also gives every walker `$ref` support for free — including keywords they never special-cased (`additionalProperties`, `patternProperties`, `prefixItems`, `not`), because the resolver walks containers reflectively. ## What's in the utility (`composio/utils/json_schema.py`) Faithful port of the TS guards: - **Cycle safety** — breaks both `$ref` cycles and live-object identity cycles with a permissive `{type: object, additionalProperties: true}` sentinel. (A per-node lazy resolver without threaded cycle tracking infinite-loops on a self-referential schema; there's a regression test for exactly this.) - **Depth caps** — `MAX_REF_CHAIN_DEPTH=100` / `MAX_NODE_DEPTH=512` raise a typed `JSONSchemaRefResolutionError` instead of exhausting the interpreter (with a `RecursionError` backstop converted to the same typed error). - **Sibling-keyword merge** — Draft 2020-12 semantics (siblings win on collision), so a `description`/`default` next to a `$ref` survives. - **External-ref passthrough** — `http(s)://` refs are left untouched and logged once for audit. - **`sentinel` mode** — a dangling `$ref` (an API schema that emits `$ref` with no `$defs` block, https://github.com/ComposioHQ/composio/issues/3307) degrades to the permissive sentinel + an LLM-visible hint, instead of aborting the tool call. The `FileHelper` uses this mode for API-sourced schemas. - **Non-mutating** — returns a new schema; `$defs`/`definitions` are stripped from the inlined root. ## Tests - `python/tests/test_json_schema.py` — 30 cases mirroring the TS `jsonSchema.test.ts` contract (chains, reflective container walk, legacy `definitions`, sibling merge, cycles, depth caps, pointer-escape decoding, array-index pointers, full `sentinel`-mode suite). - `python/tests/test_files.py` — end-to-end regressions through the public methods: download/upload behind `$ref`/`$defs` (the #3506 repro), the LLM-facing input transform, graceful degradation of a dangling `$ref`, and a **self-referential schema that must not recurse infinitely**. - `nox -s chk` (ruff + mypy) green; `nox -s tst` for the file/execution suites green. ## Notes Supersedes #3545 (third-party PR that fixed the same bug by threading `root_schema` through each walker). That diagnosis was correct; this takes the centralized route consistent with the TS SDK and adds the cycle/depth guards. No changeset — Python-package change.
2026-06-25 00:53:50 +04:00
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 {}
2025-06-17 12:58:00 +05:30
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 BlockedInternalUrlError(FileError):
"""Raised when a URL file input resolves to a non-public network address."""
class SensitiveFilePathBlockedError(FileError):
"""Raised when a local file path is refused before upload (sensitive directory or credential-like name)."""
feat(sdk): default-off auto file upload/download; add dangerouslyAllowAutoUploadDownloadFiles (#3260) ## Summary Automatic tool file upload/download for `file_uploadable` fields is **off by default** in TypeScript and Python. Callers must explicitly opt in, and uploads from local paths are constrained by a fail-closed allowlist. ## Changes - **Removed (breaking):** `autoUploadDownloadFiles` (TS) / `auto_upload_download_files` (Python) — the legacy default-on flag is gone, not just deprecated. - **New opt-in:** `dangerouslyAllowAutoUploadDownloadFiles` (TS) / `dangerously_allow_auto_upload_download_files` (Python). When `true`, `tools.get(...)` collapses `file_uploadable` schemas to `{ type: 'string', format: 'path' }` and the SDK stages local paths/URLs at execute time. - **New:** `fileUploadDirs?: string[] | false` — fail-closed allowlist for local upload paths. `undefined` → `[<home>/.composio/temp]`; `false` → reject all local paths (URLs / `File` objects unaffected); explicit `string[]` replaces the default. Components are matched on a path boundary after `realpath`. - **New:** `fileDownloadDir?: string` — directory where `file_downloadable` results are staged. - **New:** `beforeFileUpload` hook receives `source: 'path' | 'url' | 'file'` (TS) / `'path' | 'url'` (Python) so it can branch on input type. - **New (TS):** when auto-upload is **off** and an LLM-driven `tools.execute` is called against a tool with `file_uploadable` inputs, the SDK emits a one-shot warning per tool slug pointing at `composio.files.upload()` for manual staging. ## Migration To restore previous behavior: ```ts new Composio({ apiKey: process.env.COMPOSIO_API_KEY!, dangerouslyAllowAutoUploadDownloadFiles: true, // Optional: tighten the allowlist beyond the default ~/.composio/temp fileUploadDirs: ['/srv/uploads'], }); ``` ```python Composio(api_key="...", dangerously_allow_auto_upload_download_files=True) ``` If you previously passed the legacy flag, remove it. There is no transitional warning — TS and Python both reject the unknown property at the type/keyword-arg level. ## Versioning | Package | Bump | | ------- | ---- | | `@composio/core` | minor | | `composio` (Python) | minor | | Other `@composio/*` packages | patch (via changesets `updateInternalDependencies: "patch"`) | See `docs/content/changelog/04-24-26-legacy-auto-upload-config-removal.mdx` for the full migration writeup.
2026-04-28 10:55:03 +05:30
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 UnsafePathComponentError(FileError):
"""
Raised when untrusted input (a tool slug, toolkit slug, or server-supplied
filename) cannot be safely used as part of a filesystem path.
Fails closed: the SDK refuses the write rather than sanitizing the value
into something that merely looks safe.
"""
2025-06-17 12:58:00 +05:30
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
2026-02-02 20:22:04 +04:00
class ResponseTooLargeError(FileError):
"""Raised when a response exceeds the maximum allowed size."""
pass
2025-06-17 12:58:00 +05:30
class TriggerError(ToolkitError):
pass
class WebhookSignatureVerificationError(TriggerError):
"""Raised when webhook signature verification fails."""
pass
class WebhookPayloadError(TriggerError):
"""Raised when webhook payload is invalid."""
pass
fix(triggers): align create() user_id resolution across SDKs (#3723) This PR: - builds on top of https://github.com/ComposioHQ/composio/pull/3714 — addresses its code-review findings - restores TS/Python parity on slug validation: Python `create()` now validates the slug up-front and raises a new `TriggerTypeNotFound`, mirroring the TS `ComposioTriggerTypeNotFoundError` - switches both SDKs to the native `user_id` upsert field — TS drops the `& { user_id }` intersection and Python drops the `extra_body` shim (both pinned clients already expose the field) - treats a blank/whitespace `userId` (TS) or `user_id`/`connected_account_id` (Python) as missing instead of forwarding it to the backend - documents the error-contract change (`create()` no longer throws `ComposioConnectedAccountNotFoundError` — that case now surfaces from the backend) in the changeset and `ts/docs/api/triggers.md`, and bumps the changeset from `patch` to `minor` - adds Python tests for the 2FA (both-provided) path, unknown-slug, and blank `userId`; tightens the TS auto-resolve assertion and adds an empty-`userId` case ## ⚠️ Release gating Since #3714, `triggers.create()` (both SDKs) relies on the backend resolving the trigger connection from `user_id` on upsert ([ComposioHQ/platform#10932](https://github.com/ComposioHQ/platform/pull/10932)). There is **no client-side fallback** — reintroducing one would undo #3714's intent, so this is a release-sequencing requirement, not a code change here. - Do not release `@composio/core` or the Python SDK until #10932 is live in all regions. - Self-hosted / on-prem deployments must be on a backend version that includes #10932. ## Review findings addressed | # | Finding | Resolution | |---|---------|------------| | P1-1 | Coupled to unreleased backend, no fallback | Documented dependency in changeset; flagged release-gating above (no fallback by design) | | P2-2 | Cross-SDK parity on slug validation | Kept `getType` in both; added Python `TriggerTypeNotFound` mirroring TS | | P2-3 | `user_id` bridge redundant in both SDKs | TS uses native field directly; Python uses `user_id=` kwarg | | P2-4 | Error-contract change shipped silently | Documented in changeset + docs `Throws` section; `minor` bump | | P2-5 | Python missing 2FA (both-provided) test | Added `test_create_with_user_id_and_connected_account_id` | | P3 6–13 | Cleanups | undefined-assertion, `none_to_omit`, docstring, `parsedBody.data`, redundant `else`, stale comments, blank-string guard | ## Verification - TS: `vitest` 59 passed, `typecheck` clean, `eslint`/`prettier` clean - Python: `pytest tests/test_triggers.py` 69 passed, `nox -s chk` (ruff + mypy) clean
2026-07-02 11:40:47 +04:00
class TriggerTypeNotFound(TriggerError, NotFoundError):
"""Raised when a trigger type cannot be found for the given slug.
Mirrors the TypeScript SDK's ``ComposioTriggerTypeNotFoundError``.
"""
pass
2025-06-17 12:58:00 +05:30
class ActionError(ToolkitError):
pass
class TriggerSubscriptionError(TriggerError, ComposioClientError):
pass
class InvalidTriggerFilters(TriggerSubscriptionError):
pass
fix(sdk): harden pusher auth and cross-origin redirect headers (#4406) This PR: - wraps `pysher.Pusher` in `_ComposioPusher`, whose channel-auth POST carries a `(5, 15)` connect/read timeout and raises `TriggerSubscriptionAuthError` (a `TriggerSubscriptionError`) on a transport failure, a non-200, or a response without an `auth` token — pysher 1.0.8 sent it with no timeout and turned a non-200 into a bare `AssertionError` on the websocket thread, on every (re)subscribe - keeps that POST a plain `requests.post(..., timeout=...)` rather than routing it through `safe_request`: the endpoint is built from the configured Composio API base URL, a fixed trusted host, not a value from a response, and the SSRF guard would refuse a local dev base URL - validates `pusher_cluster` against `^[a-z0-9-]+$` (non-empty, at most 64 chars) before pysher formats it into `ws-{cluster}.pusher.com`, raising `InvalidPusherClusterError` that names the shape violation without echoing the value - replaces the `unittest.mock.MagicMock` stand-in for pysher's connection logger with a dedicated `logging.Logger` (`NullHandler`, `propagate=False`, disabled), so `unittest` leaves the runtime import graph while raw frames stay out of user logs; a test asserts the module source no longer mentions `unittest` - strips `Authorization`, `Proxy-Authorization`, and `Cookie` from the next hop when `ssrfSafeFetch` or `safe_request` follows a redirect to a different origin; same-origin hops keep them. Manual redirect following bypasses both `fetch`'s cross-origin rule and `requests`' `rebuild_auth`, so neither guard applied it before — the gap #4387 left out - `@composio/slim` has no mirrored source (its build copies `core/dist`), so the changeset covers `@composio/core` and `@composio/slim` as patches Verified with `pytest tests/test_triggers.py tests/test_url_safety.py tests/test_path_join_guardrail.py` (192 passed), `ruff check` / `ruff format --check` on the changed files, `mypy --config-file config/mypy.ini` on the three changed modules with the noxfile's stub pins (no issues), `vitest run test/utils/ssrfGuard.test.ts` in `@composio/core` (42 passed), `pnpm typecheck` at the root (14 tasks successful), and `oxlint` + `prettier --check` on the changed TypeScript files. https://claude.ai/code/session_016ZuBv7JhVdSYTLYcTy2VJr
2026-09-09 21:29:18 +02:00
class TriggerSubscriptionAuthError(TriggerSubscriptionError):
"""Raised when the realtime channel-auth request fails.
Covers a transport error, a timeout, a non-200 response, and a response
without an ``auth`` token.
"""
pass
class InvalidPusherClusterError(TriggerSubscriptionError, ValidationError):
"""Raised when the realtime credentials carry a malformed pusher cluster."""
pass
2025-06-17 12:58:00 +05:30
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."""
2025-09-29 15:59:28 +05:30
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
feat(connected-accounts-py): surface account_type + per-user ACL on SHARED connections Python mirror of #3392 (TypeScript SDK). Same Apollo-side feature shipped across four Hermes PRs (#9860, #9882, #9887 internal, #9902), now exposed through @composio/* Python SDK wrappers. Bumped composio-client 1.37.0 → 1.38.0 (the regenerated client published by composio-base-py#66 — contains account_type + acl_config_for_shared on LinkCreateParams, ConnectedAccountPatchParams, SessionLinkParams, the retrieve response, and the list response item). SDK changes: - composio.connected_accounts.link() now accepts `account_type` and `acl_config_for_shared`. Default behaviour (omit both) creates a PRIVATE connection exactly as before. - New composio.connected_accounts.update_acl(nanoid, *, allow_all_users, allowed_user_ids, not_allowed_user_ids) — wraps PATCH /connected_accounts/{id} with the same semantics as the TS sibling. PATCH semantics: omit a param to leave unchanged; pass [] to clear an allow/deny list. Raises ValidationError if all three are None. - composio.tool_router_session.authorize() options gain `account_type` and `acl_config_for_shared` — the /tool_router/session/{id}/link endpoint accepts the same fields. - New typed errors in composio.exceptions: * ComposioAclOnlyForSharedError (400) — wired at link() / update_acl() / authorize() catch sites via the same `acl_config_for_shared is only valid on SHARED` substring match used in the TS SDK. Verified against Apollo's createConnectedAccount.ts. * ComposioSharedAccessDeniedError (403) — exported, not yet wrapped. Wraps when Tools.execute() error mapping lands in a follow-up. * ComposioSharedConnectionNotAccessibleError (400) — exported, not yet wrapped. Wraps when ToolRouterSession.create()/patch() session- validator error mapping lands. Plumbing: - python/composio/client/types.py re-exports `link_create_params` (the generated client's TypedDicts for link create payloads, including `ACLConfigForShared`). Tests: 12 new tests in TestConnectedAccountsAcl class covering link() ACL forwarding (4), AclOnlyForShared mapping on link() (1), pass-through of unrelated BadRequestError (1), update_acl() body construction (3), empty-fields rejection (1), AclOnlyForShared mapping on update_acl() (1), pass-through of unrelated BadRequestError on update_acl() (1). 49 tests pass (37 pre-existing + 12 new); full suite 635 pass / 31 skipped. Verified: nox -s chk clean (ruff + mypy on src + tests). Version: 0.13.0 → 0.14.0 (minor — additive surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:20:01 +05:30
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.
feat(connected-accounts-py): surface account_type + per-user ACL on SHARED connections Python mirror of #3392 (TypeScript SDK). Same Apollo-side feature shipped across four Hermes PRs (#9860, #9882, #9887 internal, #9902), now exposed through @composio/* Python SDK wrappers. Bumped composio-client 1.37.0 → 1.38.0 (the regenerated client published by composio-base-py#66 — contains account_type + acl_config_for_shared on LinkCreateParams, ConnectedAccountPatchParams, SessionLinkParams, the retrieve response, and the list response item). SDK changes: - composio.connected_accounts.link() now accepts `account_type` and `acl_config_for_shared`. Default behaviour (omit both) creates a PRIVATE connection exactly as before. - New composio.connected_accounts.update_acl(nanoid, *, allow_all_users, allowed_user_ids, not_allowed_user_ids) — wraps PATCH /connected_accounts/{id} with the same semantics as the TS sibling. PATCH semantics: omit a param to leave unchanged; pass [] to clear an allow/deny list. Raises ValidationError if all three are None. - composio.tool_router_session.authorize() options gain `account_type` and `acl_config_for_shared` — the /tool_router/session/{id}/link endpoint accepts the same fields. - New typed errors in composio.exceptions: * ComposioAclOnlyForSharedError (400) — wired at link() / update_acl() / authorize() catch sites via the same `acl_config_for_shared is only valid on SHARED` substring match used in the TS SDK. Verified against Apollo's createConnectedAccount.ts. * ComposioSharedAccessDeniedError (403) — exported, not yet wrapped. Wraps when Tools.execute() error mapping lands in a follow-up. * ComposioSharedConnectionNotAccessibleError (400) — exported, not yet wrapped. Wraps when ToolRouterSession.create()/patch() session- validator error mapping lands. Plumbing: - python/composio/client/types.py re-exports `link_create_params` (the generated client's TypedDicts for link create payloads, including `ACLConfigForShared`). Tests: 12 new tests in TestConnectedAccountsAcl class covering link() ACL forwarding (4), AclOnlyForShared mapping on link() (1), pass-through of unrelated BadRequestError (1), update_acl() body construction (3), empty-fields rejection (1), AclOnlyForShared mapping on update_acl() (1), pass-through of unrelated BadRequestError on update_acl() (1). 49 tests pass (37 pre-existing + 12 new); full suite 635 pass / 31 skipped. Verified: nox -s chk clean (ruff + mypy on src + tests). Version: 0.13.0 → 0.14.0 (minor — additive surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:20:01 +05:30
Fix: use ``account_type='SHARED'``, or omit the ACL fields when
creating/updating a PRIVATE connection.
feat(connected-accounts-py): surface account_type + per-user ACL on SHARED connections Python mirror of #3392 (TypeScript SDK). Same Apollo-side feature shipped across four Hermes PRs (#9860, #9882, #9887 internal, #9902), now exposed through @composio/* Python SDK wrappers. Bumped composio-client 1.37.0 → 1.38.0 (the regenerated client published by composio-base-py#66 — contains account_type + acl_config_for_shared on LinkCreateParams, ConnectedAccountPatchParams, SessionLinkParams, the retrieve response, and the list response item). SDK changes: - composio.connected_accounts.link() now accepts `account_type` and `acl_config_for_shared`. Default behaviour (omit both) creates a PRIVATE connection exactly as before. - New composio.connected_accounts.update_acl(nanoid, *, allow_all_users, allowed_user_ids, not_allowed_user_ids) — wraps PATCH /connected_accounts/{id} with the same semantics as the TS sibling. PATCH semantics: omit a param to leave unchanged; pass [] to clear an allow/deny list. Raises ValidationError if all three are None. - composio.tool_router_session.authorize() options gain `account_type` and `acl_config_for_shared` — the /tool_router/session/{id}/link endpoint accepts the same fields. - New typed errors in composio.exceptions: * ComposioAclOnlyForSharedError (400) — wired at link() / update_acl() / authorize() catch sites via the same `acl_config_for_shared is only valid on SHARED` substring match used in the TS SDK. Verified against Apollo's createConnectedAccount.ts. * ComposioSharedAccessDeniedError (403) — exported, not yet wrapped. Wraps when Tools.execute() error mapping lands in a follow-up. * ComposioSharedConnectionNotAccessibleError (400) — exported, not yet wrapped. Wraps when ToolRouterSession.create()/patch() session- validator error mapping lands. Plumbing: - python/composio/client/types.py re-exports `link_create_params` (the generated client's TypedDicts for link create payloads, including `ACLConfigForShared`). Tests: 12 new tests in TestConnectedAccountsAcl class covering link() ACL forwarding (4), AclOnlyForShared mapping on link() (1), pass-through of unrelated BadRequestError (1), update_acl() body construction (3), empty-fields rejection (1), AclOnlyForShared mapping on update_acl() (1), pass-through of unrelated BadRequestError on update_acl() (1). 49 tests pass (37 pre-existing + 12 new); full suite 635 pass / 31 skipped. Verified: nox -s chk clean (ruff + mypy on src + tests). Version: 0.13.0 → 0.14.0 (minor — additive surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:20:01 +05:30
"""
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.
docs(py): sanitize public docstrings — remove internal references Addresses 2 review comments from @abir-taheer on #3401: - tests/test_connected_accounts.py:546 — remove Hermes PR reference - models/connected_accounts.py:708 — update_acl docstring too long; match the style of other methods in the file (cf. update()) Swept the full PR diff for the same patterns rather than just the two flagged lines: - Removed Hermes PR numbers and TS-test cross-reference from TestConnectedAccountsAcl class docstring. - Trimmed update_acl docstring — kept ~10 lines (one-line description, param list, one example) instead of the original ~40 lines with the resolution-rule table, multiple examples, and verbose footgun-warning block. Matches the style of update() / delete() / refresh() in the same file. Resolution rule + per-list caps stay documented on ConnectedAccountAclConfigSchema (where the contract belongs). - Removed internal backend error codes from exception class docstrings (``ConnectedAccount_AclOnlyForShared``, ``ConnectedAccount_SharedAccessDenied``, ``ToolRouterV2_SharedConnectionNotAccessible``). Surfacing the internal code names leaks implementation details. - Reworded "users in the project" / "project member" / "Open access to everyone in the project" — users don't belong to a project entity in Composio's data model. Replaced with phrasing focused on ``user_id`` and the pinning mechanism. - Removed "Set at create time only" wording from the link() param description on connected_accounts.py and tool_router_session.py (the corresponding upstream API description has the same wording removed). - Cleaned inline catch comments referencing internal error code names. No behaviour changes — docstring / comment cleanup only. Verified: nox -s chk clean, ruff format --check clean, 49/49 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 01:21:10 +05:30
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.
feat(connected-accounts-py): surface account_type + per-user ACL on SHARED connections Python mirror of #3392 (TypeScript SDK). Same Apollo-side feature shipped across four Hermes PRs (#9860, #9882, #9887 internal, #9902), now exposed through @composio/* Python SDK wrappers. Bumped composio-client 1.37.0 → 1.38.0 (the regenerated client published by composio-base-py#66 — contains account_type + acl_config_for_shared on LinkCreateParams, ConnectedAccountPatchParams, SessionLinkParams, the retrieve response, and the list response item). SDK changes: - composio.connected_accounts.link() now accepts `account_type` and `acl_config_for_shared`. Default behaviour (omit both) creates a PRIVATE connection exactly as before. - New composio.connected_accounts.update_acl(nanoid, *, allow_all_users, allowed_user_ids, not_allowed_user_ids) — wraps PATCH /connected_accounts/{id} with the same semantics as the TS sibling. PATCH semantics: omit a param to leave unchanged; pass [] to clear an allow/deny list. Raises ValidationError if all three are None. - composio.tool_router_session.authorize() options gain `account_type` and `acl_config_for_shared` — the /tool_router/session/{id}/link endpoint accepts the same fields. - New typed errors in composio.exceptions: * ComposioAclOnlyForSharedError (400) — wired at link() / update_acl() / authorize() catch sites via the same `acl_config_for_shared is only valid on SHARED` substring match used in the TS SDK. Verified against Apollo's createConnectedAccount.ts. * ComposioSharedAccessDeniedError (403) — exported, not yet wrapped. Wraps when Tools.execute() error mapping lands in a follow-up. * ComposioSharedConnectionNotAccessibleError (400) — exported, not yet wrapped. Wraps when ToolRouterSession.create()/patch() session- validator error mapping lands. Plumbing: - python/composio/client/types.py re-exports `link_create_params` (the generated client's TypedDicts for link create payloads, including `ACLConfigForShared`). Tests: 12 new tests in TestConnectedAccountsAcl class covering link() ACL forwarding (4), AclOnlyForShared mapping on link() (1), pass-through of unrelated BadRequestError (1), update_acl() body construction (3), empty-fields rejection (1), AclOnlyForShared mapping on update_acl() (1), pass-through of unrelated BadRequestError on update_acl() (1). 49 tests pass (37 pre-existing + 12 new); full suite 635 pass / 31 skipped. Verified: nox -s chk clean (ruff + mypy on src + tests). Version: 0.13.0 → 0.14.0 (minor — additive surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:20:01 +05:30
Fix: ask the connection's creator to grant access via
fix(connected-accounts-py): address review feedback on #3412 Six code-review nits from the multi-agent review on PR #3412: P1.1 — python/setup.py was still pinning composio-client==1.36.0 while pyproject.toml is on 1.39.0. The Experimental TypedDict that the new link() docstring imports only exists in 1.39+, so anyone installing via the legacy setup.py path would crash at import. Bumped to 1.39.0. P1.2 — exceptions.py ComposioSharedAccessDeniedError and ComposioSharedConnectionNotAccessibleError docstrings told users to call composio.connected_accounts.update_acl(), which no longer exists. Rewrote both to composio.experimental.update_acl(). P2.5 — ExperimentalAPI.update_acl returned t.Any; restored the typed ConnectedAccountPatchResponse return so callers reading .id / .status / .success keep type-safety at the API boundary. P2.6 — ExperimentalAPI.__init__(client: Optional[Any]) tightened to Optional[HttpClient]. The None branch stays for the one test that exercises it; production code in sdk.py always passes a real client. P2.7 — Dropped redundant alias=omit / connection=omit on the patch() call. Verified against the composio-client==1.39.0 wheel: both default to omit. Sibling ConnectedAccounts.update() already follows the cleaner pattern. Test assertions updated to match. P2.10 — Extracted the substring "acl_config_for_shared is only valid on SHARED" into ACL_ONLY_FOR_SHARED_ERROR_FRAGMENT in experimental.py, and reused it from connected_accounts.py + tool_router_session.py. Single source of truth so a server-side message tweak doesn't silently downgrade three call sites to generic BadRequestError. Tests: 201/201 pass. mypy on composio/ clean. Pushed back on the remaining P2 (TS divergence) in the review — the companion TS PR #3424 ships the exact same restructure, so parity is preserved once both land together. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:22:07 +05:30
``composio.experimental.update_acl()`` — set ``allow_all_users``,
feat(connected-accounts-py): surface account_type + per-user ACL on SHARED connections Python mirror of #3392 (TypeScript SDK). Same Apollo-side feature shipped across four Hermes PRs (#9860, #9882, #9887 internal, #9902), now exposed through @composio/* Python SDK wrappers. Bumped composio-client 1.37.0 → 1.38.0 (the regenerated client published by composio-base-py#66 — contains account_type + acl_config_for_shared on LinkCreateParams, ConnectedAccountPatchParams, SessionLinkParams, the retrieve response, and the list response item). SDK changes: - composio.connected_accounts.link() now accepts `account_type` and `acl_config_for_shared`. Default behaviour (omit both) creates a PRIVATE connection exactly as before. - New composio.connected_accounts.update_acl(nanoid, *, allow_all_users, allowed_user_ids, not_allowed_user_ids) — wraps PATCH /connected_accounts/{id} with the same semantics as the TS sibling. PATCH semantics: omit a param to leave unchanged; pass [] to clear an allow/deny list. Raises ValidationError if all three are None. - composio.tool_router_session.authorize() options gain `account_type` and `acl_config_for_shared` — the /tool_router/session/{id}/link endpoint accepts the same fields. - New typed errors in composio.exceptions: * ComposioAclOnlyForSharedError (400) — wired at link() / update_acl() / authorize() catch sites via the same `acl_config_for_shared is only valid on SHARED` substring match used in the TS SDK. Verified against Apollo's createConnectedAccount.ts. * ComposioSharedAccessDeniedError (403) — exported, not yet wrapped. Wraps when Tools.execute() error mapping lands in a follow-up. * ComposioSharedConnectionNotAccessibleError (400) — exported, not yet wrapped. Wraps when ToolRouterSession.create()/patch() session- validator error mapping lands. Plumbing: - python/composio/client/types.py re-exports `link_create_params` (the generated client's TypedDicts for link create payloads, including `ACLConfigForShared`). Tests: 12 new tests in TestConnectedAccountsAcl class covering link() ACL forwarding (4), AclOnlyForShared mapping on link() (1), pass-through of unrelated BadRequestError (1), update_acl() body construction (3), empty-fields rejection (1), AclOnlyForShared mapping on update_acl() (1), pass-through of unrelated BadRequestError on update_acl() (1). 49 tests pass (37 pre-existing + 12 new); full suite 635 pass / 31 skipped. Verified: nox -s chk clean (ruff + mypy on src + tests). Version: 0.13.0 → 0.14.0 (minor — additive surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:20:01 +05:30
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.
feat(connected-accounts-py): surface account_type + per-user ACL on SHARED connections Python mirror of #3392 (TypeScript SDK). Same Apollo-side feature shipped across four Hermes PRs (#9860, #9882, #9887 internal, #9902), now exposed through @composio/* Python SDK wrappers. Bumped composio-client 1.37.0 → 1.38.0 (the regenerated client published by composio-base-py#66 — contains account_type + acl_config_for_shared on LinkCreateParams, ConnectedAccountPatchParams, SessionLinkParams, the retrieve response, and the list response item). SDK changes: - composio.connected_accounts.link() now accepts `account_type` and `acl_config_for_shared`. Default behaviour (omit both) creates a PRIVATE connection exactly as before. - New composio.connected_accounts.update_acl(nanoid, *, allow_all_users, allowed_user_ids, not_allowed_user_ids) — wraps PATCH /connected_accounts/{id} with the same semantics as the TS sibling. PATCH semantics: omit a param to leave unchanged; pass [] to clear an allow/deny list. Raises ValidationError if all three are None. - composio.tool_router_session.authorize() options gain `account_type` and `acl_config_for_shared` — the /tool_router/session/{id}/link endpoint accepts the same fields. - New typed errors in composio.exceptions: * ComposioAclOnlyForSharedError (400) — wired at link() / update_acl() / authorize() catch sites via the same `acl_config_for_shared is only valid on SHARED` substring match used in the TS SDK. Verified against Apollo's createConnectedAccount.ts. * ComposioSharedAccessDeniedError (403) — exported, not yet wrapped. Wraps when Tools.execute() error mapping lands in a follow-up. * ComposioSharedConnectionNotAccessibleError (400) — exported, not yet wrapped. Wraps when ToolRouterSession.create()/patch() session- validator error mapping lands. Plumbing: - python/composio/client/types.py re-exports `link_create_params` (the generated client's TypedDicts for link create payloads, including `ACLConfigForShared`). Tests: 12 new tests in TestConnectedAccountsAcl class covering link() ACL forwarding (4), AclOnlyForShared mapping on link() (1), pass-through of unrelated BadRequestError (1), update_acl() body construction (3), empty-fields rejection (1), AclOnlyForShared mapping on update_acl() (1), pass-through of unrelated BadRequestError on update_acl() (1). 49 tests pass (37 pre-existing + 12 new); full suite 635 pass / 31 skipped. Verified: nox -s chk clean (ruff + mypy on src + tests). Version: 0.13.0 → 0.14.0 (minor — additive surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:20:01 +05:30
Fix: grant the session user access via
fix(connected-accounts-py): address review feedback on #3412 Six code-review nits from the multi-agent review on PR #3412: P1.1 — python/setup.py was still pinning composio-client==1.36.0 while pyproject.toml is on 1.39.0. The Experimental TypedDict that the new link() docstring imports only exists in 1.39+, so anyone installing via the legacy setup.py path would crash at import. Bumped to 1.39.0. P1.2 — exceptions.py ComposioSharedAccessDeniedError and ComposioSharedConnectionNotAccessibleError docstrings told users to call composio.connected_accounts.update_acl(), which no longer exists. Rewrote both to composio.experimental.update_acl(). P2.5 — ExperimentalAPI.update_acl returned t.Any; restored the typed ConnectedAccountPatchResponse return so callers reading .id / .status / .success keep type-safety at the API boundary. P2.6 — ExperimentalAPI.__init__(client: Optional[Any]) tightened to Optional[HttpClient]. The None branch stays for the one test that exercises it; production code in sdk.py always passes a real client. P2.7 — Dropped redundant alias=omit / connection=omit on the patch() call. Verified against the composio-client==1.39.0 wheel: both default to omit. Sibling ConnectedAccounts.update() already follows the cleaner pattern. Test assertions updated to match. P2.10 — Extracted the substring "acl_config_for_shared is only valid on SHARED" into ACL_ONLY_FOR_SHARED_ERROR_FRAGMENT in experimental.py, and reused it from connected_accounts.py + tool_router_session.py. Single source of truth so a server-side message tweak doesn't silently downgrade three call sites to generic BadRequestError. Tests: 201/201 pass. mypy on composio/ clean. Pushed back on the remaining P2 (TS divergence) in the review — the companion TS PR #3424 ships the exact same restructure, so parity is preserved once both land together. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:22:07 +05:30
``composio.experimental.update_acl()`` on the pinned connection,
feat(connected-accounts-py): surface account_type + per-user ACL on SHARED connections Python mirror of #3392 (TypeScript SDK). Same Apollo-side feature shipped across four Hermes PRs (#9860, #9882, #9887 internal, #9902), now exposed through @composio/* Python SDK wrappers. Bumped composio-client 1.37.0 → 1.38.0 (the regenerated client published by composio-base-py#66 — contains account_type + acl_config_for_shared on LinkCreateParams, ConnectedAccountPatchParams, SessionLinkParams, the retrieve response, and the list response item). SDK changes: - composio.connected_accounts.link() now accepts `account_type` and `acl_config_for_shared`. Default behaviour (omit both) creates a PRIVATE connection exactly as before. - New composio.connected_accounts.update_acl(nanoid, *, allow_all_users, allowed_user_ids, not_allowed_user_ids) — wraps PATCH /connected_accounts/{id} with the same semantics as the TS sibling. PATCH semantics: omit a param to leave unchanged; pass [] to clear an allow/deny list. Raises ValidationError if all three are None. - composio.tool_router_session.authorize() options gain `account_type` and `acl_config_for_shared` — the /tool_router/session/{id}/link endpoint accepts the same fields. - New typed errors in composio.exceptions: * ComposioAclOnlyForSharedError (400) — wired at link() / update_acl() / authorize() catch sites via the same `acl_config_for_shared is only valid on SHARED` substring match used in the TS SDK. Verified against Apollo's createConnectedAccount.ts. * ComposioSharedAccessDeniedError (403) — exported, not yet wrapped. Wraps when Tools.execute() error mapping lands in a follow-up. * ComposioSharedConnectionNotAccessibleError (400) — exported, not yet wrapped. Wraps when ToolRouterSession.create()/patch() session- validator error mapping lands. Plumbing: - python/composio/client/types.py re-exports `link_create_params` (the generated client's TypedDicts for link create payloads, including `ACLConfigForShared`). Tests: 12 new tests in TestConnectedAccountsAcl class covering link() ACL forwarding (4), AclOnlyForShared mapping on link() (1), pass-through of unrelated BadRequestError (1), update_acl() body construction (3), empty-fields rejection (1), AclOnlyForShared mapping on update_acl() (1), pass-through of unrelated BadRequestError on update_acl() (1). 49 tests pass (37 pre-existing + 12 new); full suite 635 pass / 31 skipped. Verified: nox -s chk clean (ruff + mypy on src + tests). Version: 0.13.0 → 0.14.0 (minor — additive surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:20:01 +05:30
or pin a different connection the user can use.
"""
pass
2025-06-17 12:58:00 +05:30
class ErrorProcessingToolExecutionRequest(PluginError):
pass
class DescopeAuthError(ComposioError):
pass
class DescopeConfigError(ComposioError):
pass
class InvalidExecuteFunctionError(ComposioError):
pass
class ToolNotFoundError(NotFoundError):
"""Raised when a tool slug does not exist.
Mirrors the TypeScript SDK's ``ComposioToolNotFoundError``. Other failures
while fetching a tool (invalid API key, server or network errors) are not
translated and surface as the underlying ``composio_client`` error.
"""
2025-06-17 12:58:00 +05:30
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={'<toolkit-slug>': '<toolkit-version>'})\n"
"3. Set the toolkit version in the environment variable "
"(COMPOSIO_TOOLKIT_VERSION_<TOOLKIT_SLUG>)\n"
"4. Set dangerously_skip_version_check to True "
"(this might cause unexpected behavior when new versions of the tools are released)"
),
)
2025-06-17 12:58:00 +05:30
class UsageError(ComposioError):
pass