Commit Graph

386 Commits

Author SHA1 Message Date
Alberto Schiabel 4fe776898a chore(toolchain): finish mise migration (#3493)
## Summary

Depends on #3492.

This completes the Phase 2 migration by removing the transitional
version-file layer and making `mise.toml` plus `mise.lock` the
repository toolchain source of truth. It also moves runtime test
matrices into `toolchain-versions.json`, so CI matrix changes are
explicit and reviewable without reintroducing `.nvmrc`, `.dvmrc`,
`.bun-version`, or `.python-version`.

The Node.js e2e matrix now starts at the latest Node 22 LTS line and
also covers the latest Node 24 and Node 25 lines. That removes Node 20
from the well-known e2e versions while keeping us covered on the
runtimes SDK users are moving toward.

## Rationale

Phase 1 introduced mise side by side with the existing version files to
keep the first PR low-risk. Phase 2 removes that compatibility layer so
there is one place to update tool versions. That avoids silent drift
between local setup, GitHub Actions, Docker E2E images, release docs,
and install-time checks.

The composite setup actions now install Node, Bun, Python, and uv
through mise by default, with explicit version overrides only where a
matrix needs them. New GitHub actions added in this PR are pinned by
release commit SHA and include the release version comment.

## What changed

- Deleted the transitional root/version files: `.nvmrc`, `.bun-version`,
`.dvmrc`, root `.python-version`, and `python/.python-version`.
- Removed `idiomatic_version_file_enable_tools` from `mise.toml` and
added a committed `mise.lock` for linux/macOS x64/arm64 tool resolution.
- Replaced `BYPASS_BUN_VERSION_CHECK` with `BYPASS_TOOLCHAIN_CHECK`, and
made Docker E2E image installs use that bypass because they receive
explicit build args instead of installing mise.
- Updated Node/Bun and Python/uv composite actions to default to mise,
remove `*-version-file` inputs, report resolved versions, and cache pnpm
after `corepack enable`.
- Centralized CI runtime matrices in `toolchain-versions.json` for TS
E2E, Python tests, and CLI npm fallback coverage.
- Updated the Node E2E matrix to `22.22.3`, `24.16.0`, and `25.9.0`,
removing Node 20 from the well-known runtime versions.
- Updated workflows, docs, E2E helpers, Dockerfiles, and release
guidance to reference `mise.toml` / `mise.lock`.

## Verification

- Verified latest Node 22/24/25 releases from the official Node dist
index: `22.22.3`, `24.16.0`, `25.9.0`
(https://nodejs.org/dist/index.json).
- `pnpm install --frozen-lockfile`
- `pnpm --filter @e2e-tests/utils typecheck`
- `pnpm --filter @e2e-tests/utils exec tsc --noEmit --target es2022
--module esnext --moduleResolution bundler --types bun
--resolveJsonModule --skipLibCheck --strict scripts/docker-build.ts`
- `bash -n ts/scripts/pre-install.sh && bun run
ts/scripts/pre-install/check-toolchain.ts && BYPASS_TOOLCHAIN_CHECK=1
bash ts/scripts/pre-install.sh`
- `pnpm exec prettier --check ...` on touched YAML/Markdown/TS/JSON
files
- `ruby -e "require \"yaml\"; ARGV.each { |f| YAML.load_file(f) }" ...`
on touched actions/workflows
- `mise lock --platform linux-x64,linux-arm64,macos-arm64,macos-x64 &&
git diff --exit-code mise.lock`
- `mise exec node@22.22.3 -- pnpm --filter @e2e-tests/utils typecheck`
- `mise exec node@22.22.3 -- pnpm --filter @e2e-tests/node-esm-basic
typecheck`
- `mise exec node@22.22.3 -- pnpm --filter @e2e-tests/node-cjs-basic
typecheck`
- `docker manifest inspect node:24.16.0-slim`
- `docker manifest inspect node:25.9.0-slim`
- `git diff --check`
2026-06-15 14:03:21 +04:00
Alberto Schiabel 2758287c87 feat(py): remove legacy custom tools (#3508)
This PR is **part 1 of 3** splitting
https://github.com/ComposioHQ/composio/pull/3505 to make the removal of
the old 2025 custom tools easier to review. It carries the **Python**
slice.

- removes the legacy `composio.tools.custom_tool` registry path:
`core.models.custom_tools`, `ExecuteRequestFn`, the old fallback
execution wiring, the security tests, and the example
- preserves the 2026 tool-router APIs: `composio.experimental.tool()`,
`composio.experimental.Toolkit`, inline custom-tool execution,
preload/attach/use flows, and `session.custom_tools()`
- bumps the Python workspace and all provider packages `0.13.1` →
`0.14.0`

## Notes

- This slice is a byte-identical subset of #3505 — the three split
branches recombine to that PR's exact tree. See #3505 for the original
local verification logs (`uv run --frozen nox -s chk`, targeted pytest);
CI re-runs per PR.
2026-06-04 23:53:26 -07:00
Alberto Schiabel 4367513f82 fix(python): guard enhance_schema_descriptions against empty schemas (#3398)
Stacked on #3397 · refs
https://github.com/ComposioHQ/composio/issues/3354

## Summary

The TS fix in #3397 handled the reported case (`output_parameters: {}`
from MCP toolkits) by normalizing empty schemas at `transformToolCases`.
While verifying the Python SDK didn't have the equivalent Zod check, I
found an **adjacent** crash on the input side.

The Python SDK does not have the same Pydantic check** —
`composio_client` types `input_parameters` / `output_parameters` as
`Dict[str, Optional[object]]`, which accepts `{}` trivially.
However, `Tools._get()` pipes every fetched tool's `input_parameters`
unconditionally through `FileHelper.enhance_schema_descriptions`
(`python/composio/core/models/tools.py:361-363`), which does:

```python
for _param, _schema in schema["properties"].items():
```

That crashes with `KeyError: 'properties'` on `schema={}`. Not reachable
for the specific `granola_mcp` tools in the original report (they all
have populated `input_parameters`), but it's the same API contract — any
MCP tool with no required inputs would hit this through the public
`Composio.tools.get(...)` call.

The sibling `FileHelper.process_file_uploadable_schema`
(`_files.py:760`) already guards this with `if "properties" not in
schema: return schema`. This applies the same guard to
`enhance_schema_descriptions`.

## Fix

```diff
 def enhance_schema_descriptions(self, schema: t.Dict) -> t.Dict:
     ...
+    if "properties" not in schema:
+        return schema
     required = schema.get("required") or []
     for _param, _schema in schema["properties"].items():
```

## Testing

Red → green confirmed locally. New
`TestEnhanceSchemaDescriptionsEmptySchema` class in
`tests/test_files.py` covers:

1. `schema={}` → returns `{}` (the bug repro).
2. Schema with metadata but no `properties` key → returned unchanged
(mirrors the sibling method).
3. Schema with `properties: {}` (empty dict) → already works today;
pinning behavior.
4. Populated schema → still enhanced (regression guard for the type-hint
/ required-marker enhancement).

Cases 1–2 **fail on `next`** with `KeyError: 'properties'`. All 4 pass
with the guard. Full `tests/test_files.py` stays green (116 tests).

```bash
cd python
.venv/bin/python -m pytest tests/test_files.py::TestEnhanceSchemaDescriptionsEmptySchema -v   # → 4 passed
.venv/bin/python -m pytest tests/test_files.py -q                                              # → 116 passed
```

## Stacked PR notes

- Base: `jkomyno/fix-mcp-empty-output-parameters` (#3397).
- This PR's diff against that base shows **only** the Python changes
(`_files.py` + `tests/test_files.py`).
- After #3397 merges, this should be retargeted to `next` (or rebased
automatically by GitHub).

---------

Co-authored-by: jkomyno <12381818+jkomyno@users.noreply.github.com>
2026-05-17 01:03:33 +04:00
Alberto Schiabel 5dcb363446 docs(py): add 0.13.1 entry to CHANGELOG (#3427)
Adds a `0.13.1` entry to `python/CHANGELOG.md` covering the changes
accumulated on `next` since the last Python release (`py@0.13.0`, May
7).

The CHANGELOG had been quietly abandoned since `0.8.11` (Sept 2025);
rather than try to backfill the 4-minor gap, this PR draws a line and
adds a banner pointing readers at the git log for that window. Future
releases start fresh from here.

## What's in 0.13.1

- **SHARED connected accounts (experimental)** —
`composio.experimental.update_acl`, `experimental={...}` on `link()` /
`session.authorize()`, `account_type` filter on `list()`. Mirrors
`@composio/core@0.10.0` shipping in #3425.
- **`initiate()` deprecation warning** — gated on response `Deprecation`
header (RFC 9745) instead of `auth_scheme`, eliminating false positives
for custom auth configs and non-OAuth schemes.
- **Schema converter** — preserves nullability on `null`-only `anyOf`
blocks.
- **Dep bump** — `composio-client` 1.36.0 → 1.39.0.

## Release plan

Documentation only — does NOT trigger a release on its own. The Python
release flow is tag-triggered:

```bash
# After this PR merges and after #3425 ships:
git tag py@0.13.1 <next HEAD sha>
git push origin py@0.13.1
```

That fires `.github/workflows/py.release.yml` which builds and publishes
to PyPI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: jkomyno <12381818+jkomyno@users.noreply.github.com>
2026-05-14 09:32:03 +02:00
Shrey Singla fccec8b143 fix(python-sdk): handle file upload list unions (#3373)
## Summary

This updates the Python SDK's automatic file upload/download
substitution so it can handle tool schemas that accept either a single
file or a list of files.

Tools can expose file inputs as a union such as:

```json
{
  "anyOf": [
    { "type": "object", "file_uploadable": true },
    { "type": "array", "items": { "type": "object", "file_uploadable": true } }
  ]
}
```

When auto-upload is enabled, the SDK presents those file-uploadable
objects to models as file path strings. If the model supplies a list of
local paths, the old resolver selected the first file-bearing union
branch, usually the single-file branch, and attempted to upload the
entire Python list as one path. That fails before the backend ever
receives the intended list of staged file descriptors.

## Changes

- Refactors upload substitution into a value/schema recursive walker via
`_substitute_file_upload_value`.
- Adds the same value/schema traversal shape for downloads via
`_substitute_file_download_value`.
- Selects composed-schema variants by runtime shape when possible, so
list values choose array branches and string/dict values keep the
single-value behavior.
- Preserves the existing fallback behavior: if no runtime shape matches,
use the first file-bearing variant.
- Treats `anyOf`, `oneOf`, and `allOf` consistently with the SDK's
existing composed-schema convention.
- Preserves root request/response dict mutation behavior for existing
call sites.
- Adds regression coverage for single-file vs multi-file unions, nested
array-item unions, download parity, and first-match fallback.

## Why

This is needed for file-capable tool schemas that are backward
compatible at the API level by accepting both a single file and multiple
files. The Python SDK should transform a list of local paths into a list
of staged `{name, mimetype, s3key}` descriptors, just as it already does
for direct array schemas.

## Verification

- `python -m ruff check python/tests/test_files.py
python/composio/core/models/_files.py`
- `python -m pytest python/tests/test_files.py -q` -> 119 passed
- `python -m pytest python/tests/test_files.py
python/tests/test_auto_upload_download_files.py
python/tests/test_upload_dir_allowlist.py
python/tests/test_tool_router_session_files.py -q` -> 166 passed
- Manual playground verification with a Gmail send using two local
attachments: both files were staged and sent successfully as an
attachment array.

Note: a full local `python/tests` run still has three unrelated failures
because `composio_langchain` is not installed in this environment; the
affected file-upload suites pass.

---------

Co-authored-by: Zen <zen@composio.dev>
2026-05-14 12:57:26 +05:30
venkat82 44972d90ad Merge remote-tracking branch 'origin/next' into feat/connected-accounts-experimental-py
# Conflicts:
#	python/composio/utils/schema_converter.py
2026-05-13 18:30:01 +05:30
venkat82 1645634866 fix(schema-converter): silence mypy on null-only-anyOf fallback return
The `return t.Optional[t.Any] if has_null else str` line introduced in
630038ab8 returns a union of `<typing special form>` and `type[str]`,
which mypy can't reconcile with the function's declared `t.Type` return.

Same `# type: ignore` pattern already used three other times in this
function (lines 280, 285, 287) for the identical reason — `t.Optional`
isn't a `type[T]`, it's a typing special form.

Unblocks Checks/common-checks on this PR and any other PR cut after
630038ab8 landed on next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:26:47 +05:30
venkat82 ed3bb936d2 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
Abir Taheer d676cd8b30 chore(py): bump all packages to 0.13.1
Patch release matching TS 0.9.1 — includes account_type + per-user ACL
on SHARED connections, SEC-339 deprecation header fix, nullability fix
in schema converter, and docstring cleanup.

Core was erroneously bumped to 0.14.0 in #3401 without a release; since
0.14.0 was never published to PyPI, all packages align at 0.13.1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-12 16:47:39 -07:00
Yizuki_Ame 630038ab83 fix: preserve nullability in null-only anyOf and fix validate param alias
Merge PR #3173 as the preferred fix for the null-only anyOf schema behavior and validate alias handling.
2026-05-12 18:20:05 +02:00
venkat82 38620df152 refactor(experimental-py): move ExperimentalAPI to its own module
ExperimentalAPI was sitting inside custom_tool.py because custom tools
were the first thing the SDK exposed on the composio.experimental
namespace. Now that update_acl has moved onto the same namespace,
keeping the class in custom_tool.py reads wrong — grepping for
update_acl lands in a file named after custom tools.

Splits the class out into core/models/experimental.py. custom_tool.py
keeps the custom-tool machinery (ExperimentalToolkit, decorator
helpers, serializers); experimental.py imports what it needs from
custom_tool.py and is now the home for anything on the
composio.experimental namespace.

Pure rearrangement — no behaviour change, no public-import-path change
(composio.core.models still re-exports ExperimentalAPI). Stack-frame
depth in _get_caller_locals(depth=2) stays correct: the
decorator → user-module hop is the same regardless of which file
ExperimentalAPI.tool lives in.

Tests still pass (201/201), mypy still clean on composio/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:53:01 +05:30
venkat82 706b01ce75 feat(connected-accounts-py): namespace SHARED-connection surface under experimental
Aligns the Python SDK with the experimental wire shape used by Shared
Connections. The flat `account_type` / `acl_config_for_shared` kwargs on
`link()` and `authorize()` have moved under a single `experimental` dict,
and `update_acl` has moved off `composio.connected_accounts` onto the
`composio.experimental` namespace — same precedent as
`composio.experimental.tool` / `composio.experimental.Toolkit`.

The `experimental` namespace is the signal that the shape may change in
future releases. Pinning a SHARED connection in a session config and
direct execute by `connectedAccountId` are unchanged — only the
connection-create / patch / authorize surfaces are namespaced.

Also surfaces the `account_type=` filter on `composio.connected_accounts.list()`
so SHARED connections can be listed without dropping to the raw client.
The wire keeps this as a flat query param (`?account_type=`), so the
SDK keeps it flat too with the experimental signal carried in the value
enum description.

Caller migration:

  # before
  composio.connected_accounts.link(
      user_id, auth_config_id,
      account_type="SHARED",
      acl_config_for_shared={"allow_all_users": True},
  )
  composio.connected_accounts.update_acl(
      "ca_abc", allow_all_users=True,
  )
  session.authorize(
      "github",
      account_type="SHARED",
      acl_config_for_shared={"allow_all_users": True},
  )

  # after
  composio.connected_accounts.link(
      user_id, auth_config_id,
      experimental={
          "account_type": "SHARED",
          "acl_config_for_shared": {"allow_all_users": True},
      },
  )
  composio.experimental.update_acl(
      "ca_abc", allow_all_users=True,
  )
  session.authorize(
      "github",
      experimental={
          "account_type": "SHARED",
          "acl_config_for_shared": {"allow_all_users": True},
      },
  )

  # new — list SHARED connections
  shared = composio.connected_accounts.list(
      account_type="SHARED",
      user_ids=["user_creator"],
  )

composio-client bumped from 1.38.0 -> 1.39.0 so the generated typed
client carries the Experimental TypedDicts for link.create,
tool_router.session.link, and connected_accounts.patch.

Tests cover: experimental block forwarding (link + authorize),
no-op when omitted, empty-list preservation, typed error mapping for
the PRIVATE-with-ACL case, experimental.update_acl body construction +
deny-list handling + ValidationError when no fields are provided +
ValidationError when called without a bound client, and
list(account_type="SHARED") flat-filter delegation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:43:25 +05:30
venkat82 613d3804af docs(py): drop residual "at create time" wording in error class docstrings
Two more spots leaking the same immutability wording removed from the
upstream API description:

- ComposioAclOnlyForSharedError docstring said "sent on a PRIVATE
  connection — at create time or via PATCH" and the Fix suggested "set
  account_type='SHARED' at create time". Reworded to drop the
  "at create time" assertion.
- ComposioSharedConnectionNotAccessibleError docstring said "caught at
  session-create time". Replaced the "caught" framing with "Raised at
  session-create time" — "session-create time" stays because it's
  user-facing (tells the SDK caller when the error is raised, i.e.
  during session.create() / session.patch(), not later during
  execution).

Also tightened the surrounding wording — class doc cross-references the
actual SDK method names (``tool_router.session.create()`` /
``session.patch()``) instead of abstract phrasing.

No behaviour changes — docstring cleanup only.

Verified: ruff check + format clean, nox -s chk clean, 49 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 01:28:00 +05:30
venkat82 d27eecce20 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
venkat82 fafaa55f88 fix(py): ruff format the new files + regenerate uv.lock for 0.14.0
Two CI / review issues on #3401:

1. "Lint and Format Check" failed — `ruff format --check` flagged
   `composio/core/models/tool_router_session.py` and
   `tests/test_connected_accounts.py`. Local `nox -s chk` only runs
   `ruff check` (lint), not `ruff format --check`, so the gap slipped
   through. Ran `ruff format` on both.

2. Bugbot caught uv.lock drift — pyproject.toml bumped to 0.14.0 but
   uv.lock still recorded 0.13.0 from when the dep bump commit was
   created. Ran `uv lock` to regenerate; the lockfile now records 0.14.0.

No behaviour changes — formatting + lockfile sync only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:30:52 +05:30
venkat82 b283fbbccc 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
Composio c358ffaa3b fix(connected-accounts): SEC-339 gate initiate() warning on response Deprecation header
initiate() previously emitted a one-time deprecation warning whenever the
response carried a redirectable OAuth scheme (OAUTH1, OAUTH2, DCR_OAUTH),
regardless of whether the auth config was Composio-managed (subject to the
2026-07-03 cutover) or custom (unaffected). The wording was conditional —
'If this auth config is Composio-managed…' — so callers using their own
OAuth apps could ignore it, but the warning still printed and added noise
to logs.

Apollo already emits the SEC-339 'Deprecation' / 'Sunset' / 'Link
rel="deprecation"' headers (RFC 9745 / RFC 8594) only on the retiring
branch — managed + redirectable OAuth + LD didn't block. The SDKs now
read the 'Deprecation' header from the response and gate the warning on
its presence. Custom auth configs and non-OAuth schemes get a clean
response from the server and now stay silent in the SDK as well.

TS:
- Chain '.withResponse()' on the underlying client.connectedAccounts.create
  call to expose the raw Response. Defensive fallback for tests that mock
  create() as a plain Promise.
- Drop the LEGACY_RETIRING_OAUTH_SCHEMES set; the apollo header is now the
  sole signal.
- New tests in describe('initiate deprecation header gate', ...) pin the
  contract: warns when Deprecation is present, silent when absent, and
  one-time-per-process even across multiple managed-OAuth calls. A
  mockApiPromiseWithHeaders helper wraps mocked values as APIPromise-shape
  thenables so the new tests can simulate apollo's header behavior.

Python:
- Switch to client.connected_accounts.with_raw_response.create so we can
  read raw.headers.get('Deprecation'). Defensive fallback to the parsed-
  only path for test mocks that don't stub with_raw_response.
- Drop _LEGACY_RETIRING_OAUTH_SCHEMES; same simplification as TS.
- New TestInitiateDeprecationHeaderGate class with the same three pinning
  cases. A _set_initiate_response helper routes mock responses through
  the with_raw_response surface that initiate() now consumes. Pre-existing
  initiate tests migrated to the helper; one assertion that inspected
  create.call_args.kwargs now reads with_raw_response.create.call_args.kwargs.

No public API change for either SDK — initiate() returns the same
ConnectionRequest shape and respects the same allowMultiple guard.
ComposioLegacyConnectedAccountsEndpointRetiredError continues to surface
from the 400 retired-path response.

Changeset bumps @composio/core as a patch.
2026-05-08 15:18:03 +05:30
Dhawal Upadhyay 156753245c chore(py): bump sdk packages to 0.13.0 2026-05-07 19:17:57 +05:30
Dhawal Upadhyay b48711c587 Clarify custom preload use behavior 2026-05-07 16:44:22 +05:30
Dhawal Upadhyay ffb7539bb5 Fix ToolRouter example staging runs 2026-05-07 16:42:08 +05:30
Dhawal Upadhyay 0c0d535275 fix(python): use generated custom tool preload types 2026-05-07 16:10:38 +05:30
Dhawal Upadhyay bccd32beae Refine ToolRouter preload docs and constants 2026-05-07 16:10:38 +05:30
Abir Taheer c96f094b15 feat(core): add session.update() for partial config updates (#3362)
## Summary
- Adds `session.update()` method to both TypeScript and Python SDKs
- Calls the existing `PATCH /api/v3/tool_router/session/{id}` endpoint
- Accepts the same config shape as `create()` (toolkits, tools, tags,
auth_configs, connected_accounts, manage_connections, workbench,
multi_account, preload)
- Mutates the session object in-place (preload, configVersion, warnings)

## Usage

### TypeScript
```typescript
const session = await composio.create('user_123', {
  toolkits: ['gmail'],
  manageConnections: true,
});

await session.update({
  toolkits: { enable: ['gmail', 'github'] },
  workbench: { enable: false },
});
```

### Python
```python
session = composio.create(user_id='user_123', toolkits=['gmail'])

session.update(
    toolkits={'enable': ['gmail', 'github']},
    workbench={'enable': False},
)
```

## Test plan
- [x] TypeScript: `tsc --noEmit` passes
- [x] TypeScript: all 874 existing tests pass
- [x] Python: mypy passes
- [x] Python: ruff lint + format clean
- [x] Python: all 82 tool_router tests pass
- [ ] Manual test: create session, call `session.update()`, verify
config changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-07 03:39:14 -07:00
Alberto Schiabel 0fd9196515 feat(connected-accounts): support REVOKED status added in Apollo #9550 (#3344)
# Description

Apollo (hermes#9550) added a new \`REVOKED\` value to the
connected-account status enum, alongside the existing \`INITIALIZING |
INITIATED | ACTIVE | FAILED | EXPIRED | INACTIVE\`. The Apollo
public-API response schema is \`z.nativeEnum(ConnectionStatusEnum)\`, so
any list / get / status-update endpoint can now return \`REVOKED\` and
clients must handle it.

This PR fixes every hand-written place in the TS and Python SDKs that
hard-coded the prior 6-value set and would either (a) fail-fast against
\`REVOKED\` (Zod / Effect Schema parse error) or (b) silently
misclassify it.

## TypeScript SDK — BREAKING fixes

| File | Change | Severity |
|---|---|---|
| \`ts/packages/core/src/types/connectedAccounts.types.ts\` | Add
\`REVOKED\` to \`ConnectedAccountStatuses\` and
\`ConnectedAccountStatusSchema\` (\`z.enum\`) | BREAKING —
\`z.enum().parse()\` would throw on responses containing the new value |
| \`ts/packages/cli/src/models/connected-accounts.ts\` | Add \`REVOKED\`
to Effect \`Schema.Literal(...)\` for \`ConnectedAccountItem.status\` |
BREAKING — \`Schema.Literal()\` enforces literal matching |
| \`ts/packages/cli/src/services/tool-router-session-connections.ts\` |
Add \`'REVOKED'\` case to the \`normalizeConnectedAccountStatus\` switch
| BREAKING — silent fall-through to \`'INACTIVE'\` masked the new status
|
| \`ts/packages/cli/src/services/composio-clients.ts\` | Replace the
explicit 6-value cast with a generic that resolves to whatever the
Stainless client currently accepts | Forwards \`REVOKED\` to Apollo;
auto-tracks future Stainless regenerations |
| \`ts/packages/core/src/models/ConnectedAccounts.ts\` | Cast
\`statuses\` to the Stainless params type so \`REVOKED\` (already
accepted by Apollo) flows through until \`@composio/client\` is
regenerated | Adapter shim; remove once \`@composio/client\` is
regenerated |

## Python SDK — DEGRADED fixes

| File | Change | Severity |
|---|---|---|
| \`python/composio/core/models/webhook_events.py\` | Add \`REVOKED\` to
\`ConnectionStatusEnum\` | DEGRADED — code that checked \`status in
ConnectionStatusEnum\` would have failed to recognise the new value |
| \`python/composio/core/models/connected_accounts.py\` |
\`ConnectionRequest.wait_for_connection\` now fails fast on terminal
non-ACTIVE states (\`FAILED\` / \`EXPIRED\` / \`INACTIVE\` /
\`REVOKED\`) instead of looping until the 60s timeout | DEGRADED —
REVOKED used to look identical to "still pending" until the timeout
fired |

The Stainless-generated \`@composio/client\` (TS) and
\`composio_client\` (Python) packages will pick up \`REVOKED\`
automatically when regenerated; this PR covers the hand-written SDK code
only.

# How did I test this PR

- \`pnpm typecheck\` (root) — clean (\`9 successful, 9 total\`)
- \`pnpm test\` (root) — 694 passed, 1 skipped (75 test files); examples
validation \`Validated 22 example packages\`
- Python: \`ruff check\` and \`ruff format --check\` clean on both
edited files
- Smoke-tested Python imports + enum membership:
  \`\`\`
  PYTHON: imports OK, REVOKED present: REVOKED
  \`\`\`

Triggered by: sarthak@composio.dev | Source: web
Session:
https://zen-api-production-4c98.up.railway.app/dashboard/#/chat/zen-14dc825802da
2026-05-07 12:19:19 +02:00
Abir Taheer df857bfdfe fix: remove extraneous f-string prefixes in Python example
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 03:17:29 -07:00
Abir Taheer 51ae6da240 fix: default requireExplicitSelection to true when multiAccount is enabled
Also makes multiAccount fields optional in ToolRouterUpdateSessionConfig
(PATCH semantics) and adds a PATCH-safe transform that doesn't inject
create-time defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 03:14:53 -07:00
Abir Taheer 0ff5bd6cc4 refactor(python): use Stainless types directly for session.update()
Drop manual camelCase→snake_case transformation and custom type
definitions. Use session_patch_params types from composio_client and
pass kwargs straight through to client.tool_router.session.patch().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 02:59:41 -07:00
Abir Taheer a9977af2c1 docs: add session.update() examples for TypeScript and Python
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 02:28:10 -07:00
Abir Taheer a0242d7b3a docs: clarify session use wording 2026-05-07 02:24:42 -07:00
Abir Taheer 26eda4606d fix(python): attach existing tool router sessions 2026-05-07 02:11:26 -07:00
Abir Taheer 4718495d10 feat(python): add session.update() for partial session config updates
Mirrors the TypeScript session.update() — calls the existing PATCH
endpoint and mutates the session's preload config in-place.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 02:07:36 -07:00
jkomyno 5bdaafd506 docs: trim verbose comments across the REVOKED-rollout commits
Net −56 lines of comment prose. Earlier commits added multi-paragraph
blocks explaining each forward-compat guard, sentinel choice, and
schema-bypass cast — most of that is rederivable from the surrounding
code. Keep one-line WHY notes where the intent is non-obvious; drop
the prose elsewhere.
2026-05-07 10:59:43 +02:00
jkomyno 27a4e6448e test(connected-accounts,py): patch time.time defensively in terminal-state test
`test_wait_for_connection_fails_fast_on_terminal_status` only patched
`time.sleep`, not `time.time`. A stuck CI runner could in principle
exhaust the 10s deadline before the first `retrieve()` call resolves,
in which case the test would assert against a
`ComposioSDKTimeoutError` instead of the `SDKError` it's actually
exercising. Match the monotonic-counter pattern used by the sibling
tests at lines 137 and 170 to remove that flake surface.

Surfaced by kieran-python review of PR #3344 (P3#4).
2026-05-07 10:56:29 +02:00
jkomyno d5bead2524 refactor(connected-accounts,py): hoist terminal_states to module-level frozenset
Promote the in-method `terminal_states = ("FAILED", "EXPIRED", "REVOKED")`
tuple to a module-level `_TERMINAL_CONNECTION_STATES: FrozenSet[str]`
constant alongside `_LEGACY_RETIRING_OAUTH_SCHEMES` (line 29). Two
benefits:

- `frozenset` signals set-membership semantics (which is exactly what
  `if self.status in ...` does), versus `tuple` which signals an ordered
  sequence.
- The TS-contract docstring travels with the constant, making the
  invariant discoverable without descending into the method body.

Surfaced by kieran-python review of PR #3344 (P3#1).
2026-05-07 10:54:23 +02:00
jkomyno a7c2d16bf3 test(connected-accounts): cover REVOKED rollout and forward-compat
Adds the test coverage flagged in PR #3344 review item P1#5:

TS (`ts/packages/core/test/connectedAccounts/connectedAccountAuthStates.test.ts`):
- `Oauth2RevokedConnectionDataSchema` parses with and without optional
  `revoked_at`
- `ConnectionDataSchema` discriminated union accepts REVOKED for both
  OAUTH2 and S2S_OAUTH2 (the preempted Apollo arm from P1#1)
- `transformConnectedAccountResponse` does not silently drop `state`
  for REVOKED on either auth scheme
- Closure test on the `ConnectionStatuses` enum so a future Apollo
  addition trips a CI failure on purpose

TS CLI (`connected-accounts.list.cmd.test.ts`):
- Server returning an unrecognized status no longer crashes the
  command — the `catchTag('ParseError')` fallback warns the user to
  run `composio upgrade` and renders the row anyway

Python (`tests/test_connected_accounts.py`):
- `wait_for_connection` fails fast on FAILED / EXPIRED / REVOKED
  (parameterized) — the contract added by this PR
- `wait_for_connection` does NOT treat INACTIVE as terminal, so a
  connection briefly transiting through INACTIVE still recovers to
  ACTIVE — guards against the regression called out in P1#2
2026-05-07 10:29:14 +02:00
jkomyno 4e1883089d fix(connected-accounts,py): drop INACTIVE from terminal states in wait_for_connection
The previous patch added FAILED / EXPIRED / INACTIVE / REVOKED to the
terminal-state set, flipping the contract from "loop until timeout for
any non-ACTIVE" to "raise SDKError immediately on any of those four".
INACTIVE means "user disabled, can be reactivated" — a connection can
briefly transit through it before settling on ACTIVE, and failing fast
there regressed callers that previously recovered.

Align Python's terminal set with TS (`ConnectionRequest.ts`) at
[FAILED, EXPIRED, REVOKED]. INACTIVE remains non-terminal in both
SDKs, matching its "reactivatable" semantics.

Addresses PR #3344 review comments P1#2 (regression on INACTIVE) and
P1#3 (TS↔Python terminal-state divergence).
2026-05-07 10:17:31 +02:00
Zen Agent 0b32b158f3 feat(connected-accounts): support REVOKED status added in Apollo PR #9550
Apollo (hermes#9550) added REVOKED to the connected-account status enum.
The SDK had several places that hard-coded the prior 6 values and would
either fail-fast against the new value (BREAKING) or silently misclassify
it (DEGRADED).

TypeScript SDK (BREAKING fixes):
- Add REVOKED to `ConnectedAccountStatusSchema` / `ConnectedAccountStatuses`
  so `z.enum().parse(...)` no longer throws on responses that include the
  new status.
- Add REVOKED to the CLI's Effect `Schema.Literal(...)` for
  `ConnectedAccountItem.status`.
- Add REVOKED to the `normalizeConnectedAccountStatus` switch so a
  REVOKED account is no longer silently coerced to INACTIVE.
- Fix the connected-account-list raw-cast to forward REVOKED into the
  Stainless-generated client (the cast widens to whatever
  `client.connectedAccounts.list` accepts so it auto-tracks regenerations).

Python SDK (DEGRADED fixes):
- Add REVOKED to `ConnectionStatusEnum` so consumers checking
  `event.data.status in ConnectionStatusEnum` recognise the value.
- Update `ConnectionRequest.wait_for_connection()` to fail fast on
  terminal non-ACTIVE statuses (FAILED / EXPIRED / INACTIVE / REVOKED)
  instead of polling until the 60s timeout.

The Stainless-generated `@composio/client` and `composio_client` packages
will pick up the new enum value automatically when regenerated; this PR
covers the hand-written SDK code only.
2026-05-07 10:13:41 +02:00
Abir Taheer e3617d1251 style(python): ruff format test_tool_router.py
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 23:36:58 -07:00
Abir Taheer a03b3759ec fix(python): update test to expect coerced array for connected_accounts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 23:33:47 -07:00
Abir Taheer bd36c57aaf fix: accept string | string[] for connectedAccounts, coerce to array
Non-breaking: existing callers passing a single string per toolkit
continue to work. The string is silently coerced to a single-element
array to match the v3.1 wire format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 22:57:40 -07:00
Abir Taheer d2b5dda2d2 fix(python): use InlineCustomToolsWirePayload type for mypy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 22:07:25 -07:00
Abir Taheer b336a089ee refactor: simplify inline custom tools payload construction
Remove conditional spreads/checks — undefined properties are stripped
during JSON serialization anyway.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 22:00:04 -07:00
Abir Taheer 3fc9cfc305 fix: update connected_accounts to array type for v3.1 multi-account
Bumps @composio/client to 0.1.0-alpha.70 which changes
connected_accounts from Dict[str, str] to Dict[str, List[str]].
Updated both TS and Python SDK types to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 21:52:23 -07:00
Abir Taheer e156769eb6 fix(python): update connected_accounts type to Dict[str, List[str]]
Matches the v3.1 wire type from composio-client 1.37.0 where
connected_accounts maps toolkit slugs to arrays of account IDs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 21:46:08 -07:00
Abir Taheer 9c9c273ba1 docs(python): remove server implementation details from use() docstring
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 21:43:01 -07:00
Abir Taheer 8d27cb0c08 style(python): ruff format tool_router.py
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 21:34:21 -07:00
Abir Taheer 797cce87fd chore(python): bump composio-client to 1.37.0
Picks up session_execute_params.Experimental type needed for inline
custom tools on v3.1 execute/search/execute_meta endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 21:18:12 -07:00
Abir Taheer 366c795e4f feat(python): add custom tools support to session.use() via v3.1 attach
Mirrors the TS SDK changes:
- composio.use(id, custom_tools=..., custom_toolkits=...) POSTs to
  /api/v3.1/tool_router/session/{id}/attach with inline experimental
  payload when customs are provided, else falls back to v3 retrieve.
- build_custom_tools_map_from_response now accepts both create and
  retrieve experimental response types.
- CustomToolsMap gains a `tools` field to cache the raw standalone
  CustomTool list for future inline re-injection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 20:25:29 -07:00
Dhawal Upadhyay 73b42a29a8 Support SDK-local custom tool preload for v3.1 (#3349)
## Summary
- Add `preload` on TS/Python custom tools and custom toolkits so
SDK-created sessions can expose selected custom schemas directly from
`session.tools()`.
- Keep top-level `preload.tools` for backend-owned Composio tool slugs /
`all`; reject custom slugs there with guidance to use the SDK custom
definition flag, and reject bare-string Python misuse except `"all"`.
- Pass inline custom definitions on create/search so backend
validation/search has current definitions without persisting them;
direct `session.execute(...)` remains generated-client typed and does
not carry custom payload.
- Preserve `sessionPreset: "direct_tools"` /
`session_preset="direct_tools"`, including explicit override precedence;
direct-tools uses `preload.tools = "all"` only when the caller does not
override preload.
- Add OpenAI Agents SDK examples for explicit preload and
`sessionPreset: "direct_tools"` in TS and Python, including
toolkit-level preload with a nested `preload: false` / `preload=False`
override.

## Validation
- `pnpm --dir ts/packages/core test test/models/toolRouter.test.ts
test/models/customToolRouting.test.ts`
- `pnpm --dir ts/packages/core typecheck`
- `env COMPOSIO_CACHE_DIR=/private/tmp/composio-cache
python/.nox/chk/bin/python -m pytest python/tests/test_custom_tools.py
python/tests/test_tool_router.py`
- `python/.nox/chk/bin/python -m ruff check
python/examples/tool_router/preload.py
python/examples/tool_router/direct_tools_preset.py
python/composio/core/models/custom_tool.py
python/composio/core/models/custom_tool_types.py
python/composio/core/models/tool_router.py
python/composio/core/models/tool_router_session.py
python/tests/test_custom_tools.py python/tests/test_tool_router.py`
- `python/.nox/chk/bin/python -m py_compile
python/examples/tool_router/preload.py
python/examples/tool_router/direct_tools_preset.py`
- `pnpm --dir ts/examples/tool-router exec tsc --noEmit --skipLibCheck
--target ES2022 --module ESNext --moduleResolution Bundler
--allowSyntheticDefaultImports --strict src/preload.ts
src/direct-tools-preset.ts`
- `git diff --check`
2026-05-06 19:06:57 +05:30
Dhawal Upadhyay 98569bcf6f Respect base URL in Python ToolRouter examples 2026-05-06 18:52:18 +05:30