mirror of
https://github.com/ComposioHQ/composio.git
synced 2026-09-22 11:46:35 +08:00
@composio/cli@0.4.2-beta.390
510 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8bb1d29950 |
fix(core): raise tool not found only on 404/400
getRawComposioToolBySlug relabelled every client error, including an invalid API key (401), as ComposioToolNotFoundError. Map only 404/400 to not-found and wrap the rest in a new ComposioToolFetchError that keeps the client error as cause. Toolkits.getToolkitBySlug compared against the OpenAI APIError class, so its not-found branch never fired; import the Composio client class instead. Python mirrors the mapping: an unknown slug raises ToolNotFoundError (now a NotFoundError), anything else propagates the composio_client error unchanged. PRDE-1613 Claude-Session: https://claude.ai/code/session_017HtbhwMAKcfebo8HyXWa5s |
||
|
|
85996c4a1d |
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
|
||
|
|
1049942edb |
fix(py): raise requests and urllib3 floors past open advisories (#4404)
This PR: - raises the `composio` package floors to `requests>=2.32.4` and `urllib3>=2.7.0` in `python/pyproject.toml`, `python/setup.py`, and `uv.lock` - closes GHSA-9hjg-9r4m-mvj7 (`requests` `.netrc` credential leak via malicious URLs) for downstream installs; `url_safety` fetches user- and server-supplied URLs through a `trust_env` session - closes GHSA-mf9v-mfxr-j63j (decompression-bomb guard bypass in the `urllib3` streaming API that `_fetch_file_from_url` relies on for its size limit) and GHSA-qccp-gfcp-xxvc (sensitive headers forwarded across origins) - the workspace lock already resolves 2.34.2 / 2.7.0, so only the `requires-dist` specifiers change; `uv lock --check` passes and `import composio` still works - documents the advisory IDs next to each floor so the next bump has context |
||
|
|
ba85f4d183 |
fix(sdk): honor Fetch redirect semantics in both SSRF guards (#4387)
This PR: - builds on top of https://github.com/ComposioHQ/composio/pull/4271, whose commit it carries unchanged - applies the Fetch standard's redirect method/body rules in **both** SSRF guards via `_redirect_rewrite` / `redirectRewrite`: a `303` retries as a bodiless request, a `301`/`302` does the same for a `POST`, and `307`/`308` replay both - narrows `ssrfSafeFetch` to the five statuses the Fetch standard calls a redirect, so a `304` or `305` carrying a `Location` is returned to the caller instead of followed — Python already used `_REDIRECT_STATUS_CODES` - drops `params` after the first hop in `safe_request`, since `Location` carries the query for the target it names and re-appending handed a query-string credential to a target that never asked for one - purges the union of the Fetch `request-body-header` set and the two `requests` also drops, identically on both sides - blocks the IPv6 transition ranges the TypeScript CIDR list missed — 6to4 `2002::/16`, Teredo and the rest of `2001::/23`, local-use NAT64 `64:ff9b:1::/48`, `100::/64`, `2001:db8::/32`, site-local `fec0::/10` — and the IPv4/IPv6 multicast and `192.88.99.0/24` ranges Python's `is_global` missed ## Context Both guards follow redirects by hand so every hop is revalidated against the address blocklist. That also means neither inherits the method and body rewriting `fetch` and `requests` would have done, so an upload answered with a `303` was replayed — payload and all — at a result URL that expects a GET. https://github.com/ComposioHQ/composio/pull/4271 landed that rule in Python only, which left the two SDKs disagreeing on the same wire behavior. Reviewing for that divergence surfaced the redirect-status set, the `params` replay, and the address-blocklist gaps above. `2002:7f00:1::` is 6to4 for `127.0.0.1`, and it passed the TypeScript guard as a public address. Verified with `pytest python/tests/test_url_safety.py` (56 passed) and `vitest run` in `@composio/core` (54 files, 1280 passed), plus `ruff`, `tsc --noEmit`, `oxlint` and `prettier`. Fail-before confirmed: 10 of the new TypeScript cases and 5 of the new Python cases fail against the unmodified guards. Two known gaps are deliberately left out, each deserving its own change: neither guard strips `Authorization`/`Cookie` on a cross-origin redirect, and a non-seekable Python body is re-sent exhausted on a `307` where TypeScript throws a bare `TypeError` on a consumed `ReadableStream`. https://claude.ai/code/session_01SB3ZJdvoqBcRrWb2toWVrX --------- Co-authored-by: ump45nose <52391318+ump45nose@users.noreply.github.com> |
||
|
|
53c024a9c8 |
fix(py): keep local results when remote multi-execute transport fails (#4386)
## Summary Follow-up to #4310. The TypeScript SDK (since #3800) catches a thrown backend call for the remote half of a mixed `COMPOSIO_MULTI_EXECUTE_TOOL` batch and turns it into one failure entry per remote slug, so completed local results are not lost. The Python SDK still let the exception escape `_route_multi_execute`, discarding every local result that had already run. This ports the TypeScript behavior so both SDKs return the same shape on a remote transport failure. Fixes # ## Changes - Catch the remote future's exception in `_route_multi_execute` and keep `str(error)`, falling back to `Remote tool execution failed` when the message is empty (same fallback as TS). - Synthesize `{response: {successful: False, data: {}, error}, tool_slug, error}` for each remote index and merge them in original request order. - Recompute `total_count` / `success_count` / `error_count` on transport failure, as TS does. - Add two regression tests mirroring the TS cases in `customToolRouting.test.ts`: local results preserved with per-tool remote errors, and the empty-message fallback. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Refactor/Chore - [ ] Documentation - [ ] Breaking change ## How Has This Been Tested? - `uv run --locked --group dev pytest tests/test_custom_tools.py -q`: 87 passed. - `uv run --locked --group dev nox -s chk`: ruff and mypy clean. - Without the source change, the new `test_remote_transport_failure_keeps_local_results` raises `RuntimeError: remote unavailable` out of `_route_multi_execute`. ## Checklist - [x] I have read the Code of Conduct and this PR adheres to it - [x] I ran linters/tests locally and they passed - [ ] I updated documentation as needed - [x] I added tests or explain why not applicable - [ ] I added a changeset if this change affects published packages (Python does not use Changesets) ## Additional context TypeScript reference: `ts/packages/core/src/models/ToolRouterSession.ts`, the `remoteErrorMessage` branch, and the test "should preserve successful local results when remote transport fails". https://claude.ai/code/session_01PAXMbiZd3qPoJ8Z9uPvEAb EOF -R ComposioHQ/composio |
||
|
|
2c4339a859 |
fix(python): preserve mixed multi-execute result order (#4310)
## Summary `ToolRouterSession._route_multi_execute` currently concatenates remote results before local results and assigns new indexes from that concatenated list. For a request such as `[LOCAL_TOOL, REMOTE_TOOL]`, callers receive `[REMOTE_TOOL, LOCAL_TOOL]`, so code that correlates `results[index]` with the requested tools can use the wrong result. This brings the Python implementation in line with the merged TypeScript behavior in [#3800](https://github.com/ComposioHQ/composio/pull/3800): preserve each tool's original request index, restore that order after local/remote execution, and then assign contiguous result indexes. Fixes # ## Changes - Preserve the original index on locally executed result entries. - Map remote sub-batch results back to their original request indexes before merging. - Sort the merged results by original index and re-index them sequentially. - Update the mixed local/remote regression test to assert request order and indexes. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Refactor/Chore - [ ] Documentation - [ ] Breaking change ## How Has This Been Tested? - `uv run --locked --group dev pytest tests/test_custom_tools.py -q` — 69 passed. - `uv run --locked --group dev nox -s tst -- tests/test_custom_tools.py` — 69 passed. - `uv run --locked --group dev ruff --config config/ruff.toml check composio/core/models/tool_router_session.py tests/test_custom_tools.py` — passed. - Ruff format check on both changed files — passed. - Verified the regression test fails on the pre-fix implementation and passes after the fix. ## Screenshots (if applicable) Not applicable. ## Checklist - [x] I have read the Code of Conduct and this PR adheres to it - [x] I ran linters/tests locally and they passed - [ ] I updated documentation as needed - [x] I added tests or explain why not applicable - [ ] I added a changeset if this change affects published packages ## Additional context This change is limited to Python multi-execute result ordering. All-local and all-remote fast paths remain unchanged. No changeset is needed because this repository does not use Changesets for Python package changes. Signed-off-by: CoralGarden52 <2193436736@qq.com> |
||
|
|
705591451c |
chore(deps): upgrade CI actions and every outdated dependency (#4381)
This PR: - upgrades every CI action to its latest release (only `changesets/action` had one: v2.1.1 -> v2.1.2, SHA-pinned) and every outdated dependency across the pnpm workspace, the docs bun workspace, and all three `uv.lock` files - moves zod to 4.5.4 everywhere first-party — catalog, docs, `@composio/json-schema-to-zod`, `@composio/claude-agent-sdk` and the zod-v4 e2e fixtures; the `*-zod-v3` fixtures stay on 3.25.76 because that is what they exercise - moves `@mastra/core` 1.52.1 -> 1.53.0, which is the ceiling rather than a preference: bisecting `ts/examples/mastra`'s `cf:dry-run` shows 1.54.0 moved the workspace/sandbox subsystem behind `@mastra/core/agent`, which drags execa (-> `npm-run-path` -> `unicorn-magic`) into the Workers bundle where esbuild cannot link it. `@mastra/mcp` is capped at 1.17.2 for the same reason — 1.17.3 wants `@mastra/core` >=1.64. The docs bun workspace mirrors that cap as an explicit devDependency plus `overrides` entry, because bun does not apply overrides to auto-installed peers - clears every production advisory that has a published fix, so the audit gate can run without `--ignore`, which does not filter a single run: it writes the advisory into `auditConfig` and exits 0 whatever else is outstanding, so the gate was passing over nine advisories - `qs` -> >=6.16.0, `fast-uri` -> >=3.1.6, `toml` -> the 4.x line, all via overrides in the existing `# temporary: … drop when` style - `extract-zip` (GHSA-jmr9-qjv8-65gv) has no fixed version to move to — 2.0.1 is the newest release and GitHub records `first_patched_version` as null — so it moves to `auditConfig.ignoreGhsas` pointing at the `extractZipSafely` mitigation that already covers it - GHSA-866g-f22w-33x8 (`@ai-sdk/provider-utils` 3.x, low) also has nothing to move to: the advisory names 3.0.98 as patched but the 3.x line stopped at 3.0.30 and GitHub records no fixed version. It only enters the tree through `@mastra/core`, which is a peer or dev dependency of every published package, so all flagged paths are private examples and e2e fixtures. It goes in `ignoreGhsas` with that rationale so the un-levelled `pnpm audit --prod` step stops posting a warning comment on every PR - widens `@composio/anthropic`'s `@anthropic-ai/sdk` peer range to include `^0.124.0`, the line its devDependency now tests against (for a `0.x` caret, `^0.120.0` excluded it); the package is in the changeset for that reason - adapts three call sites that upstream broke: `eve` 0.52 moved `ApprovalContext` to `eve/tools/approval`, `@pierre/diffs` 1.4 gave `FileDiffProps` a second type parameter, and `fumadocs-openapi` 11.4 fixed the undeclared-tag drop that a docs guard test asserted (the guard now also asserts the page positively, so it cannot pass vacuously) - drops the stale `hono` `minimumReleaseAgeExclude` entry (its comment said to after 2026-08-06) and adds an `undici` `peerDependencyRules` allowance for openai 7.10's new optional peer ## Context Some upgrades were deliberately declined, each for a reason recorded next to the pin: - `vitest`/`@vitest/ui` stay on 4.1.11 — `@cloudflare/vitest-pool-workers@0.22.0` (latest) peers on `vitest ^4.1.0` - `undici` stays on `^7` in core — `pinnedDispatcher.node.ts` documents that Node's `fetch` rejects undici 8 dispatchers - the `pnpm` catalog entry stays on `^11` to match the mise-owned toolchain - `eve` stays on 0.27.6 in docs — 0.52 changes the `defineAgent` model definition and the `useEveAgent` helpers, so `agent/agent.ts` and `components/eve-chat.tsx` fail `types:check`; migrating the docs agent is its own PR - `@earendil-works/pi-coding-agent` stays on 0.84.4 — 0.85.x imports `@earendil-works/pi-server` without declaring it, so `test/pi.test.ts` fails to load `declareOperationTags` is kept as a safety net rather than retired, even though `fumadocs-openapi` 11.4 makes it redundant: removing it changes how specs are normalised at sync time and is worth its own PR. Verified locally: `pnpm build:packages`, `pnpm typecheck`, `pnpm test`, `pnpm typecheck:examples`, `pnpm lint:examples`, `turbo cf:dry-run --filter='./ts/examples/*'`, `pnpm peers check`, `pnpm audit --prod --audit-level=high` (exit 0), frozen-lockfile installs for pnpm and bun, docs `types:check` + 542 static tests, and Python `make chk` + `make tst` (1790 passed). https://claude.ai/code/session_018evFic47PFPXuB95uRE1aw EOF -R ComposioHQ/composio |
||
|
|
7d36de8c0a |
fix(python-sdk): isolate default provider per Composio instance (#4370)
Construct a fresh OpenAIProvider per SDK instance instead of sharing a module-level singleton whose execute_tool binding was overwritten by the last-constructed instance, silently routing tool execution through the wrong client/API key. Regression test in tests/test_sdk.py. Fixes #4369 Co-authored-by: Adesh Deshmukh <adeshkd123@gmail.com> Claude-Session: https://claude.ai/code/session_015YPz5SzeScR9TkgoRi2p1F EOF -R ComposioHQ/composio |
||
|
|
85d4923507 |
fix(python): dereference $ref/$defs in Google provider (#4297)
## Summary The Python Vertex AI Google provider rebuilt tool parameter schemas from `properties` and `required` without resolving internal `$ref`/`$defs` references first. As a result, referenced properties were sent as dangling references and could not be interpreted by Vertex AI. This change dereferences internal schema references before the existing Google-specific translation. It follows the provider behavior fixed in [TypeScript PR #4288](https://github.com/ComposioHQ/composio/pull/4288). ## Changes - Dereference Google provider input schemas with the existing `dereference_json_schema` helper. - Use the resolved schema when extracting properties and required fields. - Add a regression test covering a property defined through `$ref`/`$defs`. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Refactor/Chore - [ ] Documentation - [ ] Breaking change ## How Has This Been Tested? - `pytest tests/test_google_provider.py tests/test_json_schema.py tests/test_provider.py -q -k 'not TestLangchainReservedKeywords and not TestLangchainFreeFormObjectArguments'` — 59 passed, 4 skipped, 5 deselected. - `ruff check --config config/ruff.toml providers/google/composio_google/provider.py tests/test_google_provider.py` — passed. - `ruff format --check providers/google/composio_google/provider.py tests/test_google_provider.py` — passed. - `mypy --config-file config/mypy.ini providers/google/composio_google/provider.py tests/test_google_provider.py` — passed. ## Screenshots (if applicable) Not applicable. ## Checklist - [x] I have read the Code of Conduct and this PR adheres to it - [x] I ran linters/tests locally and they passed - [x] I updated documentation as needed - [x] I added tests or explain why not applicable - [x] I added a changeset if this change affects published TypeScript packages ## Additional context This is a Python-only provider fix; no TypeScript changeset is required. No existing issue was found for the Python provider, so this PR includes the minimal reproduction and regression test directly. --------- Co-authored-by: jkomyno <alberto@composio.dev> |
||
|
|
684a392816 |
chore(python): prepare 0.21.1 release (#4361)
## Summary - bump the Python SDK and all provider package versions to `0.21.1` - regenerate the root `uv.lock` from the updated workspace metadata - keep the existing coordinated changelog as the release authority ## Verification - `pnpm test:release-workflow` - `make build` (26 artifacts) - `python -m twine check python/dist/*` |
||
|
|
ab289d6224 |
fix(sdk): preserve primitive JSON Schema semantics (#4316)
## Summary - preserve boolean, empty, null, type-array, enum, const, and scalar-constraint semantics across every Python conversion entry point - intersect Zod enum and const values with declared types and constraints, including compound JSON values - default unversioned exact validation to Draft 7 and apply inclusive and numeric exclusive bounds independently - run one byte-identical corpus through Python, Zod, and Effect so accepted and rejected inputs stay aligned - keep exact JSON Schema acceptance separate from Pydantic default materialization ## Review follow-up (second push) - Python: exact Draft 7 acceptance now wraps all three entry points (`json_schema_to_pydantic_type`, `json_schema_to_model`, `pydantic_model_from_param_schema`), so they can no longer disagree - Python: draft-4 boolean `exclusiveMinimum`/`exclusiveMaximum` (OpenAPI 3.0 style) no longer crash conversion — exact validation falls back to Draft 4, and the library input is translated to the numeric spelling - Python: ECMA-only regex patterns (look-around) no longer crash pydantic model builds — Rust-incompatible patterns fall back to Python `re` - Python: type arrays with sibling constraints no longer raise `TypeError` on valid input — constraints are scoped per member before the library sees them - Python: integral floats satisfy `integer`, `const` intersects `enum`, annotation-only schemas accept anything, and an optional property with an empty `enum` tolerates absence - Zod: typeless scalar constraints apply per instance type, and string lengths count Unicode code points instead of UTF-16 code units - Effect: draft-4 boolean exclusive bounds are enforced instead of silently ignored - `multipleOf` uses decimal scaling in all three converters (declared `divergesFromJsonSchema` on the corpus case) - shared corpus grows by 13 primitive cases; new property-based tests check acceptance against real Draft 7 oracles (hypothesis + `jsonschema` in Python, fast-check + Ajv in TypeScript) ## Verification - Python `make chk` (ruff + mypy) - Python pytest: 1,572 passed (5 langchain-extra tests need an env this sandbox lacks; unchanged from base) - `@composio/json-schema-to-zod`: 187 passed incl. 300-run fast-check property test; typecheck + build - `@composio/json-schema-to-effect-schema`: 133 passed; typecheck - `@composio/core` corpus ingress tests: 61 passed - shared Python/TypeScript corpus files are byte-identical (shasum-verified) - `git diff --check` ## Contributor context This replaces four narrow proposals after independent local reproduction: - [#4301](https://github.com/ComposioHQ/composio/pull/4301) · [Glen](https://app.tryglen.com/ComposioHQ/composio/pull/4301) - [#4302](https://github.com/ComposioHQ/composio/pull/4302) · [Glen](https://app.tryglen.com/ComposioHQ/composio/pull/4302) - [#4303](https://github.com/ComposioHQ/composio/pull/4303) · [Glen](https://app.tryglen.com/ComposioHQ/composio/pull/4303) - [#4307](https://github.com/ComposioHQ/composio/pull/4307) · [Glen](https://app.tryglen.com/ComposioHQ/composio/pull/4307) --------- Co-authored-by: simpleqt <89645338+simpleqt@users.noreply.github.com> |
||
|
|
7420927183 |
fix(sdk): qualify custom toolkit child slug mapping across Python and TypeScript (#4311)
## Summary The Python SDK treated a custom tool's `original_slug` as globally unique, rejecting valid custom toolkits that reuse common child names such as `SEARCH`, `VERSION`, or `GREP` even though the backend-assigned final slugs are toolkit-qualified (`LOCAL_ALPHA_GREP`, `LOCAL_BETA_GREP`). This ports the toolkit-qualified lookup from #3360 to Python, then fixes three response-mapping bugs found in review and applies the same fixes to the TypeScript SDK so both stay in parity. ## Changes ### Python (`composio`) - Scope custom-tool collision detection and response matching by toolkit plus original slug. - Keep bare original-slug aliases only when unambiguous; `session.execute("GREP")` raises with the final slugs to use when the slug is shared. - Preserve toolkit-qualified final slugs in `custom_toolkits()`. - `build_custom_tools_map_from_response`: raise when a response tool has local handles but no exact toolkit match instead of silently dropping it or binding another toolkit's handler; only fall back to a bare match when the response carries no toolkit identity; reject duplicate qualified response entries; derive bare-slug ambiguity from local definitions so omitting a sibling in the response never makes the survivor callable by bare name. - `custom_toolkits()` only reuses a bare alias that belongs to the same toolkit. - Docstring and Python session reference page state that bare-slug execution requires a unique original slug. ### TypeScript (`@composio/core`) - Same four fixes in `buildCustomToolsMapFromResponse` and the same guard in `customToolkits()`. - JSDoc and TypeScript session reference page updated. - Changeset: patch for `@composio/core`. ### Not changed - `COMPOSIO_MULTI_EXECUTE_TOOL` still aborts the whole batch when one item uses an ambiguous bare slug, matching current TS behavior. Switching to per-item errors is a cross-SDK design change left for a follow-up. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Refactor/Chore - [ ] Documentation - [ ] Breaking change ## How Has This Been Tested? Python: - `pytest tests/test_custom_tools.py tests/test_tool_router.py`: 181 passed. - ruff (project config) clean; mypy reports no errors in the touched files. - New tests: sibling routing, multi-execute, preload rejection, listing guard, and five response-mapping cases (no exact match, cross-toolkit binding, standalone bare fallback, unknown response tools skipped, ambiguity from local definitions, duplicate qualified entries). TypeScript: - `vitest run` in `ts/packages/core`: 53 files, 1251 passed, 2 expected failures. - `tsc --noEmit` clean; prettier and oxlint via pre-commit hook. - New tests: cross-toolkit reuse in `buildCustomToolsMap` and a new `buildCustomToolsMapFromResponse` block mirroring the Python cases. Python and TypeScript CI do not run automatically on this fork PR; a maintainer needs to approve the workflow run. ## Checklist - [x] I have read the Code of Conduct and this PR adheres to it - [x] I ran linters/tests locally and they passed - [x] I updated documentation as needed - [x] I added tests or explain why not applicable - [x] I added a changeset if this change affects published packages ## Additional context Reviewed with a second opinion from Codex (gpt-5.6-sol), which flagged the wrong-handler binding and response-derived ambiguity bugs fixed in the follow-up commits. https://claude.ai/code/session_01Y7Ni3QEBDGShSrEtwQS5bA EOF -R ComposioHQ/composio --------- Signed-off-by: CoralGarden52 <2193436736@qq.com> Co-authored-by: jkomyno <alberto@composio.dev> Co-authored-by: Alberto Schiabel <jkomyno@users.noreply.github.com> |
||
|
|
fc681b87b2 |
Fix .jpg resolving to non-standard image/jpg instead of image/jpeg (#4333)
## Problem
`composio.utils.mimetypes.guess()` maps `.jpg` to `"image/jpg"`:
```python
".jpe": "image/jpeg",
".jpeg": "image/jpeg",
".jpg": "image/jpg", # <- non-standard
```
`image/jpg` is not a registered IANA media type. It is inconsistent
with:
- its own sibling extensions `.jpe`, `.jpeg`, and `.jfif-bnl`, which all
map to `image/jpeg`
- Python's standard library: `mimetypes.guess_type("x.jpg")` returns
`image/jpeg`
- IANA, which registers `image/jpeg`
`guess()` feeds the `Content-Type` used for presigned uploads
(`_upload_to_presigned_url`), so `.jpg` files are uploaded and stored
with a non-standard content type.
## Fix
Map `.jpg` to `image/jpeg`. One line.
The reverse `_MIME_TO_EXT` map keeps its `"image/jpg": "jpg"` entry on
purpose: the SDK should still accept `image/jpg` when a server sends it
in a `Content-Type` header (liberal in what it accepts, strict in what
it produces).
## Tests
Added coverage in `tests/test_mimetypes.py`: `.jpg`/`.jpeg` in the
known-extensions table plus a dedicated test documenting that `.jpg`
resolves to `image/jpeg`. Verified the new tests fail on the old value
and pass after the fix; the full `test_mimetypes.py` (35 tests) passes.
|
||
|
|
0d28befb14 |
fix(sdk): map streamed file transport failures (#4321)
## Summary - map Python file-fetch failures that occur after response headers into the documented upload and download errors - map TypeScript RemoteFile connection and streamed-body failures into RemoteFileDownloadError while preserving blocked-URL errors - close or cancel response bodies on every exit and apply the shared 100 MiB response limit to TypeScript RemoteFile downloads This supersedes the Python-only proposal in #4305 and carries the same failure category across both SDKs. ## Independent reproduction A response double returned one chunk and then raised a connection-reset error. On current next: - Python _fetch_file_from_url leaked ConnectionError, although it did close the response - Python Tool Router URL fetch leaked ConnectionError and left the response open - TypeScript RemoteFile leaked the native fetch/body TypeError instead of RemoteFileDownloadError ## Verification - Python make chk - Python make tst: 1,490 passed - TypeScript core typecheck - TypeScript core tests: 1,245 passed, 2 expected failures - TypeScript package build: 19 packages - focused Python regression tests: 3 passed - focused TypeScript RemoteFile tests: 17 passed |
||
|
|
1d31c80eff |
fix(sdk): keep credentials private in storage and logs (#4318)
## Summary - write CLI user data, pending login sessions, and agent identities through one atomic `0600` helper - repair `0644` credential files created by older CLI versions before reading them - redact credential-shaped structured values from CLI user-context diagnostics - redact secret-shaped text at both TypeScript and Python SDK log-output boundaries, including Pusher `auth` responses and exception tracebacks - preserve Python logger compatibility: errors remain untruncated, disabled levels remain lazy, and malformed placeholders cannot expose arguments ## Local reproduction Under the normal `022` umask, `next` created a plaintext credential file with mode `0644`. The pre-fix CLI user-context and TypeScript SDK debug paths also emitted sentinel credentials. The private atomic writer changes an existing `0644` target to `0600`, and the upgrade tests now prove all three legacy credential files are tightened without changing their contents. ## Verification - CLI permission upgrade tests: 31 passed across user data, pending login, and agent identity paths - CLI source and test typechecks passed - TypeScript core logging, redaction, and Pusher tests: 17 passed - TypeScript core source and type-test typechecks passed - Python logging regression tests: 5 passed - focused Ruff, Prettier, Oxlint, and `git diff --check` passed The focused CLI runner needed a temporary local alias for the pre-existing missing `#ssrf_guard` mapping in the CLI Vitest config. The alias was removed after verification and is not part of this PR. ## Contributor context Credit to **Syed Anas Mohiuddin**, independent security researcher, for reporting the legacy CLI credential-file permission issue. Supersedes [#4300](https://github.com/ComposioHQ/composio/pull/4300) · [Glen review](https://app.tryglen.com/ComposioHQ/composio/pull/4300). The implementation also covers agent credentials, retains atomic writes, and applies redaction at the shared SDK logging boundary. |
||
|
|
28bcb190d9 |
refactor(python): collapse redundant download error handlers, cover both
Review follow-up on the download size cap. `requests.exceptions.RequestException` subclasses `OSError`, so the two handlers added for the write loop were byte-identical and the second already subsumed the first. Collapse them into one `except OSError` and say why in a comment, so the next reader does not re-add the redundant clause. Route partial-file cleanup through `_discard_partial_download`, which suppresses cleanup failures: an `OSError` from `unlink` would otherwise replace the `ResponseTooLargeError` or transport error the caller needs. Cover the two error paths that had no tests: a transport failure mid-stream and a failing write both raise `ErrorDownloadingFile` and leave no partial file behind. Without the handler the write failure escapes as a raw `OSError(28)` — the defect these pin. Claude-Session: https://claude.ai/code/session_01K1hH9PMmd6KPKdkACX553z |
||
|
|
54d07dc5f5 |
fix(python): cap automatic file download size at 100 MiB
`FileDownloadable.download` streamed the response straight to disk with no byte accounting, so an untrusted `s3url` could fill the disk. Add the same `Content-Length` pre-check plus authoritative streamed-byte counter the sibling `_fetch_file_from_url` already uses, capped at `_MAX_RESPONSE_SIZE` and overridable per call via `max_size`. Also close two gaps the write loop left open: an `OSError` from `fd.write` (disk full, permissions) escaped the documented `ErrorDownloadingFile` contract, and any failure left a partial file on disk that no caller was told about. Every failure path now unlinks the partial file; `ResponseTooLargeError` still propagates uncaught so callers see the limit. Claude-Session: https://claude.ai/code/session_01K1hH9PMmd6KPKdkACX553z |
||
|
|
3cbc7556f5 | chore(sdk): prepare Python 0.21.0 and TypeScript 0.18.0 | ||
|
|
f37c6fbec3 | docs(strict-mode): correct provider support details | ||
|
|
9feca8f95d | fix(json-schema): accept document-root references in strict mode | ||
|
|
507c4fe3a8 | fix(python): preserve explicit empty tool schemas | ||
|
|
81631f83f4 | Merge branch 'next' into fix/strict-mode-keep-optional-parameters | ||
|
|
64db269a33 |
fix(deps-dev): bump langchain-openai from 1.4.3 to 1.6.0 in /python in the pip-version group across 1 directory (#4196)
Bumps the pip-version group with 1 update in the /python directory: [langchain-openai](https://github.com/langchain-ai/langchain). Updates `langchain-openai` from 1.4.3 to 1.6.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/langchain-ai/langchain/releases">langchain-openai's releases</a>.</em></p> <blockquote> <h2>langchain-openai==1.6.0</h2> <p>Changes since langchain-openai==1.5.2</p> <p>release(openai): 1.6.0 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39762">#39762</a>) feat(core): add standard model exception types (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39538">#39538</a>) fix(openai): raise clear error on unexpected response type in <code>_create_chat_result</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39731">#39731</a>)</p> <h2>langchain-openai==1.5.2</h2> <p>Changes since langchain-openai==1.5.1</p> <p>release(openai): 1.5.2 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39719">#39719</a>) fix(openai): preserve reasoning item boundaries (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39278">#39278</a>) release(openai): 1.5.2a1 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39709">#39709</a>) feat(openai): extract gateway metadata from response headers when available (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39706">#39706</a>) chore(openai): update snapshots (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39657">#39657</a>) fix(openai): support o-series models in <code>get_num_tokens_from_messages</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38710">#38710</a>)</p> <h2>langchain-openai==1.5.2a1</h2> <p>Initial release</p> <p>release(openai): 1.5.2a1 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39709">#39709</a>) feat(openai): extract gateway metadata from response headers when available (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39706">#39706</a>) chore(openai): update snapshots (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39657">#39657</a>) fix(openai): support o-series models in <code>get_num_tokens_from_messages</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38710">#38710</a>) release(openai): 1.5.1 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39653">#39653</a>) fix(openai): preserve streamed encrypted reasoning (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39635">#39635</a>) chore(infra): support langsmith gateway in CI (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39651">#39651</a>) release(openai): 1.5.0 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39629">#39629</a>) feat(openai): support openai 3.0 SDK (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39613">#39613</a>) chore(partners): bump langgraph floor in openai and huggingface lockfiles (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39617">#39617</a>) release(openai): 1.4.3 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39485">#39485</a>) fix(openai): filter invalid tool calls from content (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39366">#39366</a>) chore(openai): update guidance for responses API for OpenAI-compatible providers (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39327">#39327</a>) chore(openai): update docstring for <code>include_response_headers</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39326">#39326</a>) release(openai): 1.4.2 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39322">#39322</a>) fix(openai): handle <code>ContextWindowExceededError</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39300">#39300</a>) chore: bump the minor-and-patch group across 3 directories with 7 updates (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39187">#39187</a>) fix(openai): filter langchain-generated content block IDs (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39209">#39209</a>) fix(openai): preserve Responses <code>text</code> options (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39204">#39204</a>) fix(openai): redact MCP <code>authorization</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39155">#39155</a>) chore(model-profiles): refresh model profile data (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39050">#39050</a>) release(openai): 1.4.1 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39045">#39045</a>) feat(anthropic,fireworks,openai): support langsmith gateway through env var (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38742">#38742</a>) fix(openai): correct <code>gpt-5.3-chat-latest</code> profile (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39009">#39009</a>) release(openai): 1.4.0 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38983">#38983</a>) chore: bump pillow from 12.2.0 to 12.3.0 in /libs/partners/openai (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38999">#38999</a>) feat(core): add <code>reasoning_effort</code> as a standard chat model parameter (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38887">#38887</a>) chore(model-profiles): refresh model profile data (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38797">#38797</a>) release(openai): 1.3.5 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38785">#38785</a>) feat(openai): support explicit prompt caching (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38762">#38762</a>)</p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/langchain-ai/langchain/commit/3478c28ef21435162cb67abbd2aaef67c7cd8981"><code>3478c28</code></a> release(anthropic): 1.6.0 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39763">#39763</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/420dfc94516f57a4d8662132a55bc532afbc6045"><code>420dfc9</code></a> release(openai): 1.6.0 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39762">#39762</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/85602c3676fbd51e098a01d7c66638719f529f84"><code>85602c3</code></a> release(core): 1.6.0 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39760">#39760</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/5c3538e83a24eaf819ecec066773a232dcd4a8e6"><code>5c3538e</code></a> fix(core): resolve postponed annotations in `StructuredTool._injected_args_ke...</li> <li><a href="https://github.com/langchain-ai/langchain/commit/9984a87fa5a6971c76cb12fc75b37ed74286b740"><code>9984a87</code></a> feat(core): add standard model exception types (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39538">#39538</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/b3e9eef13c23c3a048f9846e1592b658f85f5f94"><code>b3e9eef</code></a> chore(model-profiles): refresh model profile data (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39751">#39751</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/ded2a1fb3c06a9562d9079e42099020ecaca4060"><code>ded2a1f</code></a> fix(core): allow deserializing <code>RunnablePick</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39753">#39753</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/04ae7447d72e61841905a41b309856c5191452fb"><code>04ae744</code></a> fix(core): make <code>convert_to_openai_function</code> handle callables and non-dict ma...</li> <li><a href="https://github.com/langchain-ai/langchain/commit/37f266278d780cd7ebdfbf1891ba92066192d687"><code>37f2662</code></a> feat(langchain): support custom token_counter in ContextEditingMiddleware (<a href="https://redirect.github.com/langchain-ai/langchain/issues/3">#3</a>...</li> <li><a href="https://github.com/langchain-ai/langchain/commit/2019bf5ebe50324c548f67c2666a804343f9b772"><code>2019bf5</code></a> fix(openai): raise clear error on unexpected response type in `_create_chat_r...</li> <li>Additional commits viewable in <a href="https://github.com/langchain-ai/langchain/compare/langchain-openai==1.4.3...langchain-openai==1.6.0">compare view</a></li> </ul> </details> <br /> |
||
|
|
78fb09efdf | test(py): preserve isolated Autogen regression coverage | ||
|
|
c5690031d3 | fix(py): isolate incompatible provider dependencies | ||
|
|
8fe03eff47 |
test(json-schema): add strict-mode edge cases enumerated with a second model
Extends strict-cases.json to 68 cases with shapes enumerated independently (single-element and three-member type arrays, null-only and null-carrying enum/const properties, nested compositions, nullable objects in arrays, tuple and boolean items, conditional and dependency keywords, oneOf beside anyOf, boolean and malformed properties, $ref siblings and chains, legacy definitions next to $defs, non-string required entries, ten-level nesting) plus null-omission pairs for nullable, composed and $ref-typed arguments. Checks in the generator that derives the pinned JSON. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
84684c6c8d |
fix(python): mirror the strict-mode incompatibility and branch rules
to_strict_json_schema reports the same constructs as the TypeScript implementation (tuple items, boolean subschemas, malformed properties, oneOf beside anyOf, conditional and dependency keywords), accepts a root typed ["object"], leaves enum/const that already include null alone, and omit_null_tool_arguments follows the composition branch matching the argument's shape. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
3ca07210d3 |
test(json-schema): drive strict-mode cases from a shared corpus
strict-cases.json (one byte-identical copy per language, next to object-cases.json) pins the exact strict schema or the reported incompatibilities for 44 shapes: optional widening at every depth, nullable type arrays, compositions, enum/const wrapping, annotation stripping, keyword-named and prototype-named properties, dynamic-key and free-form objects, allOf/prefixItems, $defs recursion, dangling and external refs, malformed required, non-object roots, plus null-omission argument pairs. The TypeScript suite pins the implementation and the Python suite checks parity against the same file. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
e4d24bc04b |
fix(python): treat a non-array required as absent under strict mode
to_strict_json_schema iterated a string-valued required character by character, so a malformed required kept same-named properties non-nullable while the TypeScript implementation treats it as absent and widens every property. Both SDKs now agree. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
e6fb9f9d32 |
feat(core): keep $defs recursion under strict mode
OpenAI structured outputs support local $ref pointers, including recursive definitions, so toStrictJsonSchema no longer inlines them: $defs and definitions are normalized where they are declared, an optional $ref property is widened with an anyOf null branch, and external or dangling $refs are reported as unsupported. omitNullToolArguments follows local $refs when deciding whether a null is accepted. The Vercel provider still inlines definitions before converting to Zod, which does not follow $ref. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
15e2b72f3d |
fix(python): emit strict and keep base provider config in OpenAIResponsesProvider
The strict flag now calls the base initializer (schema_config kwargs keep working), emits strict on the wrapped tool, and mirrors the TypeScript pipeline: optional parameters become required-nullable, unsupported schemas downgrade the tool to non-strict, and null arguments the tool schema rejects are dropped before execution. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
9e958948c5 |
fix(core): block sensitive upload paths hidden behind a symlinked directory (#4218)
# Description
Found by the scheduled security audit, while checking whether a test
failure on my other PR was pre-existing. It was — and the reason it
fails is a real gap in a shipped security control.
`isBlockedSensitiveFileUploadPath` (the GHSA-hp3h-89pf-5q58 denylist)
matched deny segments **only against the symlink-resolved path**. That
catches a benign name pointing at a secret — `~/innocent-name ->
~/nested/.aws/creds`, which the existing test covers — but misses the
inverse:
| Layout | Written path | Resolved path | Blocked before? |
|---|---|---|---|
| `~/innocent -> ~/.aws/creds` | no `.aws` | **`.aws`** | yes |
| `~/.claude -> /state/claude` | **`.claude`** | no `.claude` | **no** |
`~/.claude/settings.json` resolves to `/state/claude/settings.json`,
which has no `.claude` segment, so it sailed through — and `.claude` is
on the denylist precisely because it *"may contain API keys and project
context read by assistants"*.
This layout is not exotic. Dotfile managers (chezmoi, stow, yadm) and
containerised home directories produce it routinely — Composio's own
agent sandbox image has `/home/zen/.claude -> /state/claude`. For every
user in that shape the control was silently inactive, which is the worst
failure mode for a denylist: no error, no warning, upload proceeds.
The same hiding trick applies to the basename check (`~/.env ->
/state/plain-config`), so that path is fixed too.
## Why CI never caught this
`ts/packages/core/test/utils/sensitiveFileUploadPaths.test.ts` **already
asserts** the blocked behaviour:
```ts
expect(isBlockedSensitiveFileUploadPath(path.join(os.homedir(), '.claude', 'settings.json'))).toBe(true);
```
That assertion has been failing on `next` on any machine where
`~/.claude` is a symlink. It passes in CI only because `~/.claude` does
not exist on the runners: `existsSync` is false, no `realpath` runs, and
the written path keeps its `.claude` segment. The test is
environment-dependent, so green CI was never evidence the control
worked.
## The fix
The TypeScript `normalizePath` helper now returns both the written and
resolved segments, and the segment scan and basename check each consider
both. The Python guard now applies the same rule. Either path can carry
the denied name, so both SDKs inspect both forms.
# How did I test this PR
**The TypeScript fix is gated by tests — 3 fail without it, 13/13 pass
with it.**
Without the `src` change (test file only):
```
× blocks common credential directory segments
× blocks a sensitive directory that is itself a symlink to a plain path
× blocks a denied basename whose symlink target is named innocuously
Tests 3 failed | 10 passed (13)
```
With the fix:
```
Test Files 1 passed (1)
Tests 13 passed (13)
```
Note the first of those three is the **pre-existing** assertion quoted
above — this PR turns it green rather than adding it.
Three tests added, each building a real symlink in a temp dir:
- sensitive directory that is itself a symlink to a plain path (the
`~/.claude -> /state/claude` case), asserting both
`isBlockedSensitiveFileUploadPath` and that `assertSafeFileUploadPath`
throws
- denied basename whose symlink target is named innocuously (`.env ->
plain-config`)
- **negative case**: an ordinary file reached through a symlinked
directory (`docs/document.pdf`) is still allowed, so the fix does not
over-block
The Python parity change adds the same three cases. Before the Python
source change, the sensitive written directory and basename both
returned `False`; with the fix, all 11 focused Python tests pass.
**Full verification:**
| Command | Result |
|---|---|
| `vitest run` in `ts/packages/core` | **48 files, 1114 tests passed** |
| `pnpm typecheck` (workspace) | **14/14 tasks successful**, exit 0 |
| `oxlint` on both changed files | exit 0, clean |
| `prettier --check` on both changed files | "All matched files use
Prettier code style!" |
| `nox -s chk` in `python/` | Ruff and mypy passed |
| `nox -s tst` in `python/` | **1339 passed, 33 skipped**, exit 0 |
# Security
- No dependency changes, no new network calls, no new imports. The diff
is limited to the equivalent TypeScript and Python guards, their tests,
and the required `@composio/core` patch changeset.
- This **strengthens** an existing control and cannot weaken it: the
previous match set is a strict subset of the new one, so nothing that
was blocked before is allowed now. The added negative test pins that the
widening does not over-block ordinary files.
- **Grype** — `grype dir:ts/packages/core --only-fixed --fail-on medium`
→ reported below.
- **Socket** — could not run; `doppler secrets get SOCKET_API_TOKEN
--plain --project hermes --config dev_zen` returns empty in this cron
sandbox, so `socket ci` exits `Auth Error`. Reporting rather than
skipping silently.
- Unrelated pre-existing note: the repo's `pnpm audit --prod` comment
flags `extract-zip <=2.0.1` with `Patched versions >=2.0.2`, a version
that does not exist on npm. Details in #4217.
Origin: cron-48e51eab745f /
[zen-cron-44e260352d1a](https://zen.corp.composio.io/dashboard/#/chat/zen-cron-44e260352d1a)
Triggered by: saransh@composio.dev | Source: unknown
Session:
https://zen.corp.composio.io/dashboard/#/chat/zen-cron-44e260352d1a
|
||
|
|
43545ec889 | fix(python): block sensitive paths hidden by symlinks | ||
|
|
52f8861c95 |
docs(gemini): update to use gemini 3.7 and new models (#4244)
## Summary Updates docs and sample scripts to use latest Gemini models. ## Changes - Vertex & Gemini sample scripts updated - Plus accompanying markdown ## Type of change - [ ] Bug fix - [ ] New feature - [ ] Refactor/Chore - [x] Documentation - [ ] Breaking change ## How Has This Been Tested? Visual inspection ## Checklist - [x] I have read the Code of Conduct and this PR adheres to it - [x] I ran linters/tests locally and they passed - [x] I updated documentation as needed - [ ] I added tests or explain why not applicable - [ ] I added a changeset if this change affects published packages ## Additional context |
||
|
|
4ee3ff83bc | fix(python): preserve null nested file containers | ||
|
|
96c6260b8c | Merge branch 'next' into 37f-docs | ||
|
|
e3d2c093e3 | docs(gemini): update to use gemini 3.7 and new models | ||
|
|
cf25f25363 | fix(python): preserve nullable file upload arguments | ||
|
|
fe66cbeb77 |
fix(sdk): omit empty file-uploadable arguments from tool execution
Both SDKs forwarded "" for a file_uploadable parameter (e.g. Gmail attachment) verbatim to the backend, which rejected it with a Pydantic validation error. Python only dropped it inside the opt-in auto-upload walker; TypeScript never did, and with auto-upload on it tried to upload the empty string. Run a schema-aware, upload-free pass on the default execute path that omits empty file values, and reuse the same walker for staging when auto-upload is enabled. Closes #4233 |
||
|
|
ec24c54ffc |
fix(deps-dev): bump langchain-openai
Bumps the pip-version group with 1 update in the /python directory: [langchain-openai](https://github.com/langchain-ai/langchain). Updates `langchain-openai` from 1.4.3 to 1.6.0 - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-openai==1.4.3...langchain-openai==1.6.0) --- updated-dependencies: - dependency-name: langchain-openai dependency-version: 1.5.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: pip-version ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
0421f1fb52 |
chore(deps): stop Dependabot proposing ag2 majors for composio-autogen (#4236)
## Summary Closes #4197 as not-actionable and stops Dependabot from re-raising it. `ag2` 1.0 removed the top-level `autogen` package. The 1.x wheel ships only `ag2`: ``` $ python -c "import zipfile; print(sorted({n.split('/')[0] for n in zipfile.ZipFile('ag2-1.0.2-py3-none-any.whl').namelist()}))" ['ag2', 'ag2-1.0.2.dist-info'] ``` `composio_autogen/provider.py` imports `autogen`, `autogen.agentchat.register_function`, and `autogen.agentchat.conversable_agent.ConversableAgent` — none of which exist in 1.x. #4197 widened the requirement to `<2.0` and CI resolved `ag2==1.0.2`, which failed the fresh-install import guard on all three Python versions: ``` File ".../composio_autogen/provider.py", line 6, in <module> import autogen ModuleNotFoundError: No module named 'autogen' ``` This is the same failure mode as #3728 (pyautogen 0.10 shipping no `autogen`), one framework rename later. Adopting ag2 1.x is a provider rewrite against the new `ag2.tools` / middleware API, not a requirement widening — so it needs its own PR, not an automated bump. ## Changes - `.github/dependabot.yml`: ignore `version-update:semver-major` for `ag2` in the pip ecosystem, matching the existing convention for compatibility-boundary majors. Security updates are unaffected. - Record why the `<1.0` cap exists in both `pyproject.toml` and `setup.py`, so the next reader doesn't widen it by hand. ## Follow-up Migrating `composio-autogen` to ag2 1.x remains open and unscheduled. Remove the ignore entry when that lands. |
||
|
|
41be258076 |
fix(examples): fail tool_router_mcp when COMPOSIO_API_KEY is unset (#4222)
Spotted while fixing the provider-dependency failures in #4221. ## Problem `python/examples/tool_router/tool_router_mcp.py` guarded a missing credential like this: ```python api_key = os.environ.get("COMPOSIO_API_KEY") if not api_key: print("Error: COMPOSIO_API_KEY environment variable not set") print("Please set it using: export COMPOSIO_API_KEY='your_api_key'") return ``` `return` exits the coroutine normally, so `asyncio.run(main())` completes and the process exits **0**. The live-examples harness scores entries by exit code, so a run that did nothing at all was recorded **green** — the worst kind of failure for a canary, since it reports success while testing nothing. ## Fix Raise instead, using the `require_env` helper the sibling examples already use (`examples/tool_router/preload.py`): ```python def require_env(name: str) -> str: value = os.environ.get(name) if not value: raise RuntimeError(f"Set {name} before running this example.") return value ``` ## Verification Replaying the harness's own invocation with the variable unset: ``` $ unset COMPOSIO_API_KEY $ uv run --project python --with 'openai-agents>=0.19' \ --with ./python/providers/openai_agents --with ./python \ python python/examples/tool_router/tool_router_mcp.py before: exit=0 # silently "passes" after: exit=1 # RuntimeError: Set COMPOSIO_API_KEY before running this example. ``` `ruff check`, `ruff format --check`, and `nox -s chk_examples` all pass. ## Scope I checked whether other examples share the pattern. `examples/tool_router/authorize.py` prints the same message, but calls `exit(1)` at module level rather than returning from a coroutine, so it already fails correctly — left alone. This was the only instance. |
||
|
|
2f6a8a5ec9 |
fix(python): own the proxy_execute response shape (#4180)
> ### ⚠️ Breaking change > > `proxy_execute()` now returns a dict instead of the generated `SessionProxyExecuteResponse` model. Every caller since `py@0.11.4` that reads the result with attribute access breaks at runtime with `AttributeError`. > > ```python > # before > response.status > > # after > response["status"] > ``` > > `data`, `headers`, and `binary_data` follow the same rule. No version bump or changelog entry ships in this PR. That omission is deliberate, so the release call stays explicit. Details below. ## Summary Builds on @AseemPrasad's #4163, which spotted a real problem. Python's `proxy_execute()` returns the generated client's `SessionProxyExecuteResponse` directly, while TypeScript's `proxyExecute()` projects onto a curated shape. Returning the generated model leaks a regenerated artifact into a public SDK return type. This PR keeps that fix and resolves the review findings on top. #4163's commit is preserved with its original authorship. The commits on top carry the correction and the review fixes. ## What changed relative to #4163 | | #4163 | Here | |---|---|---| | Key casing | `binaryData`, `contentType`, `expiresAt` | `binary_data`, `content_type`, `expires_at` | | `status` type | declared `int`, returned `200.0` | declared `int`, returns `200` | | Test doubles | `SimpleNamespace` | real `SessionProxyExecuteResponse` / `BinaryData` | | `mypy` | fails `nox -s chk` | clean | | Docs | 3 snippets left broken | fixed | **Casing.** Python public APIs use snake_case and TypeScript public APIs use camelCase. The fields and their meanings match across SDKs, and the spelling follows each language. `session.delete()` already works this way (`session_id` in Python, `sessionId` in TypeScript), and so does `RemoteFile` (`expires_at` / `expiresAt`). **`status` and `size` are narrowed to `int`.** The generated model types both as `float` and pydantic coerces, so a response read straight off it renders `200.0` where TypeScript renders `200`. #4163 declared `int` but still returned `200.0`. That mismatch also failed `nox -s chk`: ``` composio/core/models/session_context.py:56: error: Incompatible types (expression has type "float", TypedDict item "status" has type "int") [typeddict-item] ``` **Tests use the real generated models again.** `SimpleNamespace` accepts any attribute name and any type, so it silently tolerates a client regeneration that renames or retypes a field. It was also what hid the `float` coercion, since `assert result == {"status": 200}` passes against `200.0`. The suite now asserts the narrowed types directly. This matters ahead of the `composio-client` 2.x migration, which types every response field as `Any` and removes type checking on this projection entirely. The tests become the only remaining check. **Simplification.** The projection folds into `proxy_execute_impl`, so both entry points are a single call rather than an impl-then-normalize pair. `response.binary_data` is read directly instead of through `getattr(..., None)`. The defensive default could never fire on a typed response, but it made mypy infer `Any` and stop checking the projection. **Docs.** Three Python snippets that read the result as attributes are fixed, and the response-shape table gets a per-language column. The follow-up commit also marks `headers` and `data` as nullable in that table, replaces the "returns the upstream response verbatim" claim with what the projection actually does, and documents that `expires_at` can be absent in TypeScript and `None` in Python. ## Breaking change The method has shipped since `py@0.11.4`. Both directions of the old access pattern were already inconsistent in the repo. `python/examples/custom_tools_agent_test.py:95` does `res["status"]`, which raises `TypeError` on `next` today and is fixed by this PR. The doc snippets did attribute access and are updated here. No changelog entry and no version bump are included. That is deliberate, so the release call stays explicit rather than implied by the merge. ## How Has This Been Tested? ```bash cd python mypy --config-file config/mypy.ini composio/ tests/ # clean ruff check --config config/ruff.toml composio/ tests/ # clean pytest tests/ # 1336 passed, 33 skipped ``` `ruff format` was run with the repo's pinned toolchain. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Refactor/Chore - [ ] Documentation - [x] Breaking change ## Checklist - [x] I ran linters/tests locally and they passed - [x] I updated documentation as needed - [x] I added tests or explain why not applicable - [ ] I added a changeset if this change affects published packages. Not applicable: `AGENTS.md` reserves changesets for published TypeScript packages https://claude.ai/code/session_01GsD8zvAhrjFwk144oWkD9K --------- Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Co-authored-by: Kshitij Jhunjhunwala <113939507+KJ-11@users.noreply.github.com> |
||
|
|
e57661755b |
Merge branch 'next' of https://github.com/ComposioHQ/composio into asimcomposio
# Conflicts: # python/composio/core/provider/_openai_responses.py # ts/packages/providers/openai/src/OpenAIResponsesProvider.ts |
||
|
|
04817cb20d | fix(openai): recursive strict-mode schema normalization for structured outputs (+ Python parity) | ||
|
|
5608d89cbc |
docs(auth): point custom OAuth callback URL to v1
Consumer product and the backend-provided default redirect URI use the v1 callback (api/v1/auth-apps/add), but the developer docs instructed the v3.1 URL (api/v3.1/toolkits/auth/callback). Users copied the wrong URL into their OAuth app allow lists. Align all instructional docs + the Python example with v1. The migration guide (migration-guide/new-sdk.mdx) is left unchanged: it documents the v1->v3 history and flipping it would make it contradictory. Fixes UXE-261. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a14f9d537f |
fix(py): normalize toolkit-less tool metadata (#4178)
This PR: - closes #4152 - normalizes model- and mapping-shaped tool responses at the Python SDK boundary - uses `unknown` for direct/schema modifiers and `composio` for Tool Router execution, matching TypeScript - caches the untouched fetched tool before schema modifiers so execution keeps the original toolkit version metadata - reuses the cached tool during execution instead of retrieving the same schema twice - adds behavior regressions for schema, before/after execution, single-fetch execution, raw metadata preservation, and TypeScript parity - verifies the Python regressions against both the current Stainless client and `ComposioHQ/composio-client` at `605f508e` |
||
|
|
d544006a25 |
fix(sdk): pin the validated address when fetching URLs (SSRF DNS rebinding) (#4172)
Fixes #4151. ## The problem Both SDKs validated a URL by resolving its hostname, and then handed the *hostname* to the HTTP client, which resolved it again when it opened the socket. Two lookups, two answers: a short-TTL record under an attacker's control answers publicly for the check and with `169.254.169.254`, `127.0.0.1`, or RFC 1918 space for the connect. The guard passes and the connection lands inside the network — classic TOCTOU DNS rebinding, documented in both modules until now as a known residual. ```mermaid sequenceDiagram participant SDK participant DNS as Attacker DNS participant Meta as 169.254.169.254 Note over SDK,Meta: before SDK->>DNS: resolve evil.example.com (validate) DNS-->>SDK: 93.184.216.34 — passes the guard SDK->>DNS: resolve evil.example.com (connect) DNS-->>SDK: 169.254.169.254 SDK->>Meta: GET /latest/meta-data/… Meta-->>SDK: credentials ``` ## The fix Resolve once, validate every answer, then connect to the address that was validated. There is no second lookup left to rebind. - **Python** — `safe_get` / `safe_request` mount a transport adapter that swaps the connect target for the duration of the socket connect only. The `Host` header and TLS SNI keep the hostname, so certificate verification is unchanged; rewriting `conn._dns_host` for the whole connection would have sent `Host: <ip>` and offered the IP as SNI, failing against every real origin. Every fetch call site now goes through those two helpers, so no `requests.get` sits next to a bare check any more: - `_files.py::_fetch_file_from_url`, `_files.py::FileDownloadable.download` - `tool_router_session_files.py::_fetch_url_bytes` - `safe_request`, per redirect hop - **TypeScript** — `assertSafeFetchTarget` returns the validated address and `ssrfSafeFetch` hands `fetch` a dispatcher pinned to it, re-pinned per redirect hop. The dispatcher goes to the runtime's own `fetch`, so callers that stub `globalThis.fetch` keep working. The pinned `lookup` answers both shapes Node calls it with — the address *list* it uses for Happy Eyeballs, and the single `(address, family)` it uses when `autoSelectFamily` is off — since answering in the wrong shape is rejected as an invalid address. - A fail-closed peer assertion runs on the Python side before a byte is written to the socket — redundant while pinning works, and a tripwire if a urllib3 upgrade ever breaks it. - `workerd` is unchanged: it already fails closed for user-supplied URLs. Redirect *validation* already existed in both SDKs (`safe_request` / `ssrfSafeFetch`); what was missing was re-pinning each hop. ## Tests The existing suites could not express this bug: they mock both the resolver and the HTTP client, so check and use are the same mock. The new tests use real sockets. - `python/tests/test_url_safety_pinning.py` — two loopback servers and a resolver that answers the first lookup with one endpoint and every later one with another, which is what a short-TTL rebinding record does. Asserts the rebound endpoint receives **zero** connections, and that `Host` still carries the hostname. Both tests fail on `next` and pass here. - `ts/packages/core/test/utils/pinnedDispatcher.node.test.ts` — a real server plus a hostname under `.invalid`, which RFC 2606 guarantees never resolves. A request that arrives proves the connect used the pinned address and never consulted DNS. The third case shows the contrast: unpinned, the same fetch cannot resolve at all. - `ssrfGuard.test.ts` gains assertions that each hop is pinned to that hop's own validated address. - `pinnedDispatcher.node.test.ts` also pins with `setDefaultAutoSelectFamily(false)`, which is the branch Node takes for the single-address callback. ## Notes - Supersedes #4157, which diagnosed this correctly. Its post-response peer check turned out not to hold: with an HTTP/1.0 or `Connection: close` server, urllib3 detaches the socket (`conn.sock is None`) while `r.content` still returns the full body, so the check fails open exactly where exfiltration succeeds. That is why the assertion here runs at connect time instead. - The Python package now declares `urllib3>=2` directly. `url_safety` imports it for `NameResolutionError`, which only exists from 2.0, and the pinning adapter reaches into 2.x connection internals; `requests` alone allows 1.x, where `import composio` would have failed outright. - `@composio/core` gains an `undici` dependency, pinned to `^7`: undici 8 dispatchers are rejected by the `fetch` in every Node version this package supports (22/24/25, verified). The real-socket test runs on the full CI matrix, so a future incompatibility fails loudly instead of silently un-pinning. - `undici` is imported on first pinned request rather than at module load: importing it installs a process-wide global dispatcher, which would have handed the host application's own unrelated `fetch` calls this package's undici merely because it imported `@composio/core`. - Residuals, now documented in the modules: - Requests routed through an environment proxy keep the pre-flight check only. The proxy resolves the hostname itself and the SDK cannot see or pin that resolution. - A process that does perform a pinned fetch still ends up on this package's `Agent` if nothing had claimed the global dispatcher slot yet. undici defines that slot non-configurable, so it cannot be handed back — assigning `undefined` leaves the runtime's own `fetch` asserting on a missing dispatcher. |
||
|
|
e3534e7ea6 |
chore(sdk): prepare Python 0.20.0 and TypeScript 0.17.0 releases (#4170)
This PR: - bumps `composio` and all 13 provider distributions from `0.19.0` to `0.20.0`, keeping `python/composio/__version__.py` and `uv.lock` aligned with package metadata - adds `docs/content/changelog/08-19-26-sdk-releases.mdx`, the combined customer-facing changelog for both SDKs, documenting the session-aware provider tool-call helpers from #4098 (Python `composio` 0.20.0, TypeScript `@composio/core`/`@composio/slim` 0.17.0, `@composio/anthropic` 0.11.0, `@composio/openai` 0.12.0) and the API-response URL validation and Python file-handling fixes shipped since the last train - documents `@composio/core` `0.17.0` in that entry so the generated Changesets release PR (#4161) passes the release-workflow guard ## Release sequence 1. Merge this PR. 2. Merge #4161 (the generated "Release: update version" PR) once it goes green. Merging publishes the TypeScript packages to npm. 3. Tag the resulting `next` commit `py@0.20.0` to publish the Python packages to PyPI. 4. Follow-up PR: bump `docs/package.json` pins to `@composio/core@^0.17.0`, `@composio/anthropic@^0.11.0`, `@composio/openai@^0.12.0` and remove the now-stale `@errors: 2345` Twoslash TODO markers in the provider docs, once the npm publish lands. ## Verification - Verified the release-workflow guard reads the new changelog rows for Python `0.20.0` and TypeScript `@composio/core` `0.17.0` - All 13 Python `pyproject.toml`/`setup.py` pairs bumped consistently; `uv.lock` regenerated |
||
|
|
760f8d0367 |
fix(sdk): route provider tool calls through sessions (#4098)
## Problem Provider tool-call helpers always used the globally injected direct `Tools.execute` function. When a model received tools from `session.tools()`, calling `handleToolCalls` or `handle_tool_calls` therefore discarded the Tool Router session context and caused session meta-tools such as `COMPOSIO_SEARCH_TOOLS` to fail. Calling `session.execute()` manually preserved the session, but bypassed provider behavior such as Anthropic input normalization and schema-alias restoration. ## Root fix - Add an explicit execution target to the non-agentic provider helpers: - TypeScript: `handleToolCalls(session, response)` and `executeToolCall(session, call)` - Python: `handle_tool_calls(response=response, session=session)` and `execute_tool_call(tool_call=call, session=session)` - Route normalized provider arguments through the supplied Tool Router session. - Map session responses back to each helper's existing result shape. - Keep provider-specific normalization before execution, including Anthropic schema-alias restoration. - Reject direct-only options and modifiers when the selected target is a session, including plain JavaScript calls that bypass the TypeScript overloads. - Update OpenAI and Anthropic examples to use the session-aware helpers. - Harden the docs policy test so setup and execution split across fences in one sample are still detected. ## Docs review follow-ups - Reword the concepts-page prohibition so it forbids user-ID-bound helper calls, not the helpers themselves, matching the provider pages in this PR. - Add minimum-version callouts to the OpenAI and Anthropic provider pages (Python `composio` newer than 0.19.0; TypeScript `@composio/core` ≥ 0.17.0 with `@composio/openai` ≥ 0.12.0 / `@composio/anthropic` ≥ 0.11.0), pointing older versions at `session.execute()`. - Bump `docs/package.json` to `@composio/core` `^0.15.0` and `@composio/openai` `^0.11.0` (the published majors at the time of the bump; `@composio/core` 0.16.0 and `composio` 0.19.0 have since released from `next` without this PR, so its changeset will publish core 0.17.0 and the next Python minor) and annotate each `@errors: 2345` Twoslash marker with a TODO naming the minor version that retires it; since this changeset releases minors, all three pins need a manual range bump to retire the markers. This version of twoslash only throws on *unlisted* errors, so a stale marker cannot break the build — it would only mask future TS2345s, which the TODOs now track. - Update `SESSION_GUARDRAILS` (the block appended to `.md` responses for agents): add a session-execution bullet (scoped to the OpenAI and Anthropic helpers, with `session.execute()` for every other provider) and qualify the direct-execution list with "with a user ID". The session-execution static test now scans the guardrail blocks like the execute-version test already did. - Tighten the docs detector: the Python branch is bounded to the helper call's argument list (tolerating one level of nested calls) instead of running past the closing paren, and the TypeScript branch catches whole user-ID identifiers (`userId`, `user_id`, `uid`) without flagging session variables like `userSession` — each edge has a regression test. - Note on the Google provider page that its `executeToolCall` is not session-aware yet. ## Compatibility and release Existing user-ID calls remain unchanged and continue to use direct tool execution. The new session call forms are additive. The changeset applies minor releases to `@composio/core`, `@composio/openai`, and `@composio/anthropic` — the new session overloads are a type-level break for provider subclasses, so patch was too small. The configured fixed group also includes `@composio/slim`. The docs site intentionally checks examples against currently published SDK declarations. The three new TypeScript calls therefore carry exact Twoslash `TS2345` release-skew annotations; remove them (per the inline TODOs) once `docs/package.json` picks up `@composio/core` ≥ 0.17.0, `@composio/openai` ≥ 0.12.0, and `@composio/anthropic` ≥ 0.11.0. ## Verification - `@composio/core`: 1,061 tests passed; typecheck passed - `@composio/openai`: 34 tests passed; typecheck passed - `@composio/anthropic`: 53 tests passed; typecheck passed - Python provider and aliasing suites: 40 passed, 4 skipped - Focused Python mypy and Ruff checks passed - Docs static suite: 208 tests passed (including the new guardrail-scan and detector cases) - Docs production build passed with the bumped `@composio/core` 0.15.0 / `@composio/openai` 0.11.0, including Twoslash, TypeScript, and all generated pages - Docs lint passed; lint reports only existing warnings - Changeset status reports the expected minor packages --------- Co-authored-by: Soumya Medapati <soumyamedapati@soumyas-air.local.meter> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: jkomyno <alberto@composio.dev> |