This PR:
- fixes#4023, where Python provider Pydantic models silently reduced
property-less object arguments such as
`METABASE_POST_API_CARD.dataset_query` to `{}`
- fixes#4064, where the Zod and Effect converters rejected the same
free-form object content
- fixes#4022, where Google Gemini rejected nested property-bearing
schemas that omitted `type: "object"`
- supersedes https://github.com/ComposioHQ/composio/pull/4037 with
complete Python handling for open, closed, pattern-constrained, and
schema-valued dynamic keys
- supersedes https://github.com/ComposioHQ/composio/pull/4065 with one
cross-SDK contract covering Zod, Effect, Python, CLI validation, and
provider boundaries
- supersedes https://github.com/ComposioHQ/composio/pull/4077 by
incorporating its complete-schema Claude Agent SDK registration and root
constraint handling
- supersedes https://github.com/ComposioHQ/composio/pull/4047 with its
corrected Google-only normalization, including property-map and
reserved-key safety
- keeps https://github.com/ComposioHQ/composio/pull/4094 as a stacked
Python-provider follow-up because framework serialization and CrewAI
error handling are downstream of schema conversion
- includes minor Changesets for `@composio/core`,
`@composio/json-schema-to-zod`, and `@composio/claude-agent-sdk`; the
fixed release group also bumps `@composio/slim`, and `@composio/google`
carries a patch Changeset
A tool argument declared as a bare `{ "type": "object" }` could be
rejected, reduced to `{}`, or lose its root constraints depending on the
SDK boundary. This PR makes TypeScript and Python agree on free-form
objects and dynamic keys, and protects that agreement with a shared
corpus.
## Relationship to other work
### Superseded PRs
- #4037 fixes the narrow Python property-less-object reproduction by
allowing Pydantic extras, but does not enforce `patternProperties`,
schema-valued `additionalProperties`, or closed-object rejection. This
PR preserves the reported Metabase payload while enforcing the complete
object contract.
- #4065 fixes property-less objects in the Zod and Effect converters.
This PR incorporates that behavior, expands the dynamic-key semantics,
and proves parity with Python and the provider-facing paths.
- #4077 preserves root constraints through `ToolSchema.parse` and the
Claude Agent SDK. This PR incorporates that work and tests it against
the same cross-SDK corpus.
- #4047 targets #4022. This PR incorporates its Google schema
normalization with a structure-aware walker that preserves property
maps, instance values, and reserved keys.
### Follow-up and adjacent PRs
- #4094 is intentionally separate and stacked on this PR. This PR
defines which arguments a JSON Schema accepts and what
Pydantic/Zod/Effect preserve. #4094 handles the later framework boundary
where CrewAI, LangChain, and LangGraph serialize an already-validated
model, restoring aliases, distinguishing absent values from explicit
nulls and defaults, preserving dynamic extras, and returning structured
CrewAI validation failures.
- #4093 is the narrower CrewAI-only proposal for #4092. #4094
incorporates that fix and broadens it to the two other providers that
use the same generated Pydantic models, so #4093 is not duplicated here.
Keeping #4094 separate leaves this PR focused on the shared cross-SDK
conversion contract. It also isolates provider-specific framework hooks
and Python error-shape behavior, which have a different compatibility
and test surface and do not require TypeScript Changesets.
## What changed
- Property-less object schemas are open by default. Their content is
accepted and preserved at the root, in nested objects, and in array
items. An explicit `additionalProperties: false` still closes them.
- Objects that declare `properties` remain strict when
`additionalProperties` is omitted. This is Composio's established
tool-input behavior.
- `ToolSchema.parse` now accepts bare object roots, keeps root
`patternProperties`, accepts schema-valued `additionalProperties`, and
exposes optional `properties` in its public type.
- The Zod and Effect converters apply every matching `patternProperties`
schema. A schema-valued `additionalProperties` applies only to keys not
claimed by a declared property or pattern.
- Python validates dynamic values with Draft 7 JSON Schema before
Pydantic materializes defaults. This avoids coercive or incomplete
Pydantic representations changing whether input is accepted.
- Python supports local JSON Pointer references in dynamic-key schemas.
Unresolved, external, and anchored references fail during model
construction instead of silently weakening validation.
- Accepted Python dynamic keys remain available through both
`model_dump()` and attribute access, which is required by agentic
providers such as LangChain.
- Gemini now exposes free-form object arguments as `Dict[str, Any]` in
the generated callable signature.
- `@composio/google` adds missing object types before emitting Google
GenAI declarations while preserving property maps and instance-valued
schema annotations.
- The Claude Agent SDK provider registers the complete object schema
instead of a raw property map, so root rules reach MCP validation.
- The Effect converter reports dynamic-key schema defects while
compiling the schema, rather than as input errors blaming the arguments.
`@cfworker/json-schema` compiles `patternProperties` keys and resolves
`$ref` lazily, so the defect previously reached the caller as an input
validation failure. The scope matches Python's: `patternProperties`
keys, and references inside a `patternProperties` or schema-valued
`additionalProperties` subschema, followed transitively. References
elsewhere stay with the interpreter, so a dangling reference in a branch
validation never visits keeps working.
## Behavior changes
Three existing fail-open paths now reject invalid input:
1. Python agentic providers reject unknown keys when an object declares
properties and omits `additionalProperties`, instead of dropping those
keys. This also covers a property-less object that sets
`additionalProperties: false`, which Python previously left to
Pydantic's default `extra="ignore"`.
2. The Claude Agent SDK provider rejects undeclared arguments through
MCP validation instead of stripping them and running the tool.
3. The CLI rejects a tool schema whose `patternProperties` key is not a
valid regular expression, or whose dynamic-key subschema carries an
unresolvable reference, when it compiles the schema. This can fail a
call that previously succeeded: the interpreter surfaced such a defect
only if validation descended into that part of the schema, so a payload
that stayed out of the affected branch ran against a schema that could
not fully validate it.
Gemini is affected differently: it uses `json_schema_to_pydantic_type`
to build a callable signature, but does not use `json_schema_to_model`,
so the agentic-provider unknown-key behavior does not apply.
## Cross-SDK contract
The TypeScript and Python fixtures contain the same 24 cases and are
required to remain byte-identical:
```text
ts/packages/core/test/fixtures/json-schema-conversion/object-cases.json
python/tests/fixtures/json-schema-conversion/object-cases.json
```
The corpus covers open and closed objects, overlapping patterns,
schema-valued additional properties, empty schemas, enums, nested strict
containers, false schemas, type unions, local references, JSON integer
semantics, default materialization, and intentional Composio strictness.
Each case has one shared acceptance result. Converter-specific expected
outputs are separate because Zod and Pydantic materialize defaults while
Effect validates without transforming output. Any acceptance difference
requires an explicit divergence reason.
The decoding rules and the cross-language invariants have one
definition. `@composio/core` owns the loader at
`ts/packages/core/test/fixtures/json-schema-conversion/corpus.ts`, and
the Zod, Effect, and CLI suites re-export it rather than each carrying a
copy. It reads the corpus through `resolveJsonModule`, which is already
enabled in every consumer, so no test project needs `@types/node` to
load a fixture.
## Releases and documentation
- Three explicit minor Changesets cover `@composio/core`,
`@composio/json-schema-to-zod`, and `@composio/claude-agent-sdk`.
- `@composio/core` and `@composio/slim` are a fixed Changesets group, so
the core Changeset automatically schedules the matching minor release of
`@composio/slim`; no duplicate slim Changeset is required.
- `@composio/google` has a patch Changeset and requires `@composio/core
>=0.16.0 <1.0.0`, preventing installation with older Core versions that
lack the shared normalizer.
- `@composio/json-schema-to-effect-schema` is private. The CLI and CLI
local tools are excluded from Changesets, so the CLI's user-facing fix
is recorded in `ts/packages/cli/CHANGELOG.md`.
- Python does not use Changesets. It now declares `jsonschema>=4.23.0`
directly in both package metadata sources, and the generated `uv.lock`
is refreshed with the repository-pinned uv version.
- The docs changelog distinguishes Python agentic-provider behavior,
Gemini signature behavior, and Claude Agent SDK registration behavior.
## Verification
Local verification after the final implementation:
- Python: `nox -s tst` (`1002 passed, 34 skipped`) and `nox -s chk`
- Gemini provider: `32 passed` with the provider installed editable
- TypeScript: core `1088`, Zod `118`, Effect `65`, Claude Agent SDK
`24`, Google `14`, and focused CLI validation `9`
- Affected TypeScript typechecks
- `pnpm validate:changesets` and Changesets status against the PR exact
`next` base
- Detached Changesets versioning and a strict-peer packed consumer:
Core/Slim `0.16.0`, Google `0.10.2`, Zod `0.3.0`, and Claude Agent SDK
`0.11.0`
- Python sdist and wheel build, with both artifacts passing `twine
check`
- Prettier, Ruff formatting, `uv lock --check`, byte-identical fixture
comparison, and `git diff --check`
Closes#4022, #4023, and #4064.
The catalog is gated to selected orgs, so pointing every reader at a Skills
tab most of them cannot see would send them looking for a nav item that
isn't there. The capability itself is not gated — skills already reach every
org through the search response — so the rest of the page stands on its own.
The section was also stale on its own terms: it advertised app chips showing
per-toolkit counts, which read the catalog-stats endpoint that has since been
removed, and described browse ordering as "maturity / most-refined" after the
list was changed to say most used.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This PR:
- closes#4102
- disables Git 2.54 automatic maintenance for the docs data update job
while actions/checkout lacks the upstream fix
- prevents background maintenance from racing the shallow fetch of
docs/auto-update-data before create-pull-request pushes it
- keeps the change limited to the affected workflow
- verifies the workflow with actionlint v1.7.12
This PR:
- builds on top of https://github.com/ComposioHQ/composio/pull/4055
- resolves a tool's toolkit against what the CLI already knows — the
slugs baked in at build time
(https://github.com/ComposioHQ/composio/pull/4055) plus the ones this
machine has learned since — so the common case costs one small local
file read and no network. Every tool execution needed the toolkit, and
the catalog was ~800 KB and ~2 s of the wall time of a command that then
does one small API call
- resolves that local knowledge once per run: `Effect.cached` on the
catalog service means one file read, one parse, and one lookup set per
process, where a single `composio execute` resolves a toolkit four times
over (bootstrap telemetry, the execute context, the connection check,
the execution itself) and `--parallel` multiplies that by the number of
tools
- falls through to the catalog only for a slug that matches nothing
known, which is what a toolkit released after the binary looks like, and
records what it finds in `known-toolkit-slugs.json` — so it costs a
fetch once rather than every run. The recording forks at most once per
run: every miss in a run merges the same memoized fetch, so later forks
could only rewrite an identical file
- refreshes learned slugs in the background once they are older than a
week, which bounds how long a brand-new toolkit can be mistaken for a
shorter one sharing its prefix
- gives that background refresh ten seconds, then abandons it. A
successful command ends when the event loop drains, not on an explicit
`process.exit`, so an unbounded background fetch would hold the exiting
`composio` process open until the network answered — visible to `&&`
chains, `$(...)`, and CI step timing. An abandoned refresh costs a fetch
on a later run, nothing more
- keeps `FORCE_USE_CACHE` semantics intact: the learned file is derived
data, not saved API responses, so it is read unconditionally. That
distinction is documented in the module and in the README
- cannot fail a command — a missing, unreadable, or corrupt file means
"nothing learned yet", and an unreachable catalog still degrades to the
first-underscore guess
- writes cache files through a temp file and a rename, so an interrupted
run leaves the previous cache intact instead of a truncated
`toolkits.json` the next run reads back as a parse failure
- drops the `toolkit` field from the cached tool model, which nothing
reads once resolution goes through the catalog service
- measured on a logged-in binary: `composio execute
GOOGLE_ANALYTICS_RUN_REPORT --dry-run` goes from ~8.0-8.6 s before the
stack, to ~3.4-4.3 s after
https://github.com/ComposioHQ/composio/pull/4054, to ~1.7-2.0 s here —
startup and the tool-schema fetch only
- no visible command surface changed, so no VHS recording
## Summary
Resolution now reads like this, with the catalog reached only on a
genuine miss:
```mermaid
graph TD
A[tool slug] --> B{session meta tool?}
B -->|yes| C[no toolkit]
B -->|no| D[baked slugs + learned slugs]
D --> E{longest prefix match?}
E -->|yes| F[toolkit slug]
E -->|no| G[fetch catalog]
G -->|match| H[toolkit slug, slug remembered]
G -->|unreachable| I[first-underscore guess]
```
Twelve tests cover the branches: baked hit and learned hit with zero
fetches, first run seeding the file, a miss that fetches and persists,
an unreachable catalog degrading to the guess, a corrupt file tolerated,
a stale file refreshing in the background, a fresh file not refreshing,
meta tools resolving to nothing without a fetch, the same run answering
from its memo instead of re-reading the file, and a second recording in
one run never reaching the file.
This PR:
- builds on top of https://github.com/ComposioHQ/composio/pull/4054
- checks in `src/generated/toolkit-slugs.ts` — the 1070 toolkit slugs
the CLI knows without asking the API. Resolving a tool slug to its
toolkit needs the catalog, but the catalog is ~800 KB of metadata to
learn ~11 KB of slugs
- adds `scripts/generate-toolkit-slugs.ts` (`pnpm --filter @composio/cli
generate:toolkit-slugs`), which paginates the production catalog, keeps
`native` toolkits, sorts and dedupes. It authenticates with
`COMPOSIO_API_KEY` (CI) or `COMPOSIO_USER_API_KEY` + `COMPOSIO_ORG_ID`
(a logged-in developer); both paths verified against production
- gates the write: fewer than 1000 slugs, a malformed slug, or a missing
staple (`github`, `gmail`, `google_analytics`) aborts before touching
the file, so a bad or wrong-scoped credential fails loudly instead of
overwriting a good list with a partial catalog
- adds `.github/workflows/cli.update-toolkit-slugs.yml`, a weekly cron
that opens a refresh PR when the catalog moves. `workflow_dispatch` only
works once this lands on the default branch
- warns during binary builds when the baked list is older than two
weeks, and never fails on it — a stale list is slow, never wrong,
because the backend does not remove toolkits
- adds a guard test: sorted, unique, lowercase-slug-shaped, staples
present, no network
- nothing reads the list yet;
https://github.com/ComposioHQ/composio/pull/4055 wires it into
resolution
- internal only — no changelog entry, no visible command surface, no VHS
recording
## Context
On the cron secret: `docs-update-data.yml` documents
`secrets.COMPOSIO_API_KEY` as staging-scoped and uses its own
`secrets.COMPOSIO_DOCS_API_KEY` for production reads. This workflow uses
the shared secret as decided, and the sanity gate is what makes that
safe to try — a wrong-scoped key 401s, writes nothing, and opens no PR.
The alternative is named in a comment next to the `env:` block, one line
to swap if the first run fails.
## Follow-up folded in
The toolkit-slug shape now has one definition: `TOOLKIT_SLUG_PATTERN`
lives in `src/models/toolkits.ts` and is shared by the schema, the
generation script's sanity gate, and the generated-output guard test.
This PR:
- builds on top of https://github.com/ComposioHQ/composio/pull/4053
- memoizes the toolkit catalog for the lifetime of the cached repository
layer with `Effect.cached`. A single command asks for the full list
several times — toolkit resolution, validation, telemetry — and the file
cache only answers when `FORCE_USE_CACHE` is set, so the default path
paid for the same ~800 KB two-page download up to four times
- fixes a doubling underneath it: `createCachedEffect` wrapped its whole
body — cache lookup and the underlying request alike — in one `catchAll`
that fell back to re-running the request, so a request that failed on
its own was immediately sent again and failed the same way. Cache
concerns now degrade to a cache miss on their own, and the request runs
exactly once with its failure passed straight through
- `Effect.cached` memoizes the outcome rather than only the success, so
a failed fetch stays failed for the process. That is the right trade for
a one-shot CLI, where retrying a broken network per call only multiplies
the wait before the same error surfaces
- adds a counting-repository test stub and four tests: one fetch for N
sequential callers, one for N concurrent callers, one attempt for a
failing fetch, and a `FORCE_USE_CACHE` hit served with zero fetches
(that last one passes against the old implementation too, so it pins
existing behavior rather than blessing the rewrite)
- measured on a logged-in binary: `composio execute
GOOGLE_ANALYTICS_RUN_REPORT --dry-run` goes from ~8.0-8.6 s to ~3.4-4.3
s
- no visible command surface changed, so no VHS recording
This PR:
- closes https://github.com/ComposioHQ/composio/issues/4049
- resolves a tool's toolkit by matching the tool slug against the known
toolkit list, longest underscore-bounded prefix first, instead of
splitting on the first underscore — `GOOGLE_ANALYTICS_RUN_REPORT`
belongs to `google_analytics`, not `google`, and the old split picked
the wrong connected account
- routes every call site (execute, listen, tools-executor, file uploads,
error overrides, telemetry) through one Effect resolver,
`toolkitFromToolSlug`, so they cannot disagree
- keeps the first-underscore split as `guessToolkitFromToolSlug`, used
only when the toolkit list cannot be loaded
- fixes a second miss that the old split hid: `COMPOSIO_SEARCH_TOOLS` is
a session meta tool, but `composio_search` is a real linkable toolkit,
so matching against the real catalog made a failed meta call tell users
to link an app that had nothing to do with it. The meta tool slugs move
out of `tools-executor.ts` into `src/utils/meta-tool-slugs.ts`, and
resolution answers "no toolkit" for them before any prefix matching
- no visible command surface changed, so no VHS recording
## Context
This is correct but not yet fast: resolution still reads the toolkit
catalog, which is ~800 KB over two pages. Three stacked PRs follow that
make the common case cost no network at all.
Every tool execution had to know which toolkit the tool belongs to, and
the only reliable source was the toolkit catalog — ~800 KB and ~2 s of
the wall time of a command that then does one small API call.
Match against what the CLI already knows instead: the slugs baked in at
build time, plus the ones this machine has learned since, kept in
`known-toolkit-slugs.json`. A slug that matches nothing known still
falls through to the catalog — that is what a toolkit released after the
binary looks like — and what it finds is remembered, so it costs a fetch
once rather than every run. Learned slugs older than a week trigger a
background refresh; that bounds how long a brand-new toolkit can be
mistaken for a shorter one sharing its prefix.
The file is derived data rather than saved API responses, so it is read
regardless of FORCE_USE_CACHE, which keeps its meaning. Nothing here can
fail a command: a missing, unreadable, or corrupt file means "nothing
learned yet", and an unreachable catalog still degrades to the
first-underscore guess.
Cache writes also move to a temp file and a rename, so an interrupted
run leaves the previous cache intact instead of a truncated file that
the next run reads back as a parse failure.
Resolving a tool slug to its toolkit needs the catalog, but the catalog
is ~800 KB of metadata to learn ~11 KB of slugs. Check the slugs in as
generated source so the CLI starts out knowing them.
Adds the generator (paginates the production catalog, keeps native
toolkits, sorts and dedupes), the checked-in list, a guard test, and a
weekly workflow that opens a refresh PR when the catalog moves. The
generator refuses to write a list that is short, malformed, or missing
staple toolkits, so a bad or wrong-scoped credential aborts before
overwriting a good list. The binary build warns when the list is older
than two weeks, and never fails on it.
Nothing reads the list yet.
A single command can ask for the full toolkit list several times —
toolkit resolution, validation, telemetry — and the file cache only
answers when FORCE_USE_CACHE is set, so the default path paid for the
same ~800 KB two-page download up to four times.
Memoize the lookup for the lifetime of the cached repository layer. The
first caller pays and the rest await the same result. Effect.cached
memoizes the outcome rather than only the success, so a failed fetch
stays failed for the process: the right trade for a one-shot CLI, where
retrying a broken network per call only multiplies the wait before the
same error surfaces.
That surfaced a second doubling underneath. The caching helper wrapped
its whole body — cache lookup and the underlying request alike — in one
catchAll that fell back to re-running the request, so a request that
failed on its own was immediately sent again and failed the same way.
Cache concerns now degrade to a miss on their own, and the request runs
exactly once with its failure passed straight through.
Tool slugs carry no delimiter between the toolkit and the action, so
splitting on the first underscore resolved GOOGLE_ANALYTICS_RUN_REPORT to
`google` instead of `google_analytics` and picked the wrong connected
account.
Match the tool slug against the known toolkit list instead, longest
underscore-bounded prefix first, and keep the old split as the fallback
for when that list is unavailable. Routed through a new Effect resolver
so every call site (execute, listen, tools-executor, file uploads, error
overrides, telemetry) shares one answer.
Matching against the real catalog exposes a second miss the old split
hid: `COMPOSIO_SEARCH_TOOLS` is a session meta tool, but `composio_search`
is a real linkable toolkit, so a failed meta call told users to link an
app that had nothing to do with it. The meta tool slugs move out of
tools-executor into src/utils/meta-tool-slugs.ts, and resolution answers
'no toolkit' for them before any prefix matching.
Fixes https://github.com/ComposioHQ/composio/issues/4049
Reuse fetched raw tool schemas for provider-wrapped execution while preserving modifier isolation, runtime validation, and unknown-slug fallback behavior.
Refresh the Python 3.12.13 mise lock entries required by the Audit freshness gate.
Co-authored-by: breezeFur <voidexl@outlook.com>
The loader was copied into four packages, identical but for one path constant.
Core now owns it and the Zod, Effect, and CLI suites re-export it.
It also read its fixture with node:fs, which pulled @types/node into the Zod and
Effect test typechecks for the first time and broke them against the Node 26 line
the workspace was pinned to. That had been worked around by centralizing
@types/node on a Node 24 catalog entry across ten manifests. resolveJsonModule is
already enabled in every consumer, so a static JSON import removes the node:fs
dependency and the whole detour with it: the catalog entry, the pnpm override, the
per-package types fields, and 171 lines of lockfile churn are all reverted.
compileToolInputSchema still runs inside validateToolInputArgumentsWithDefinition,
so the failure fires on every invocation. What changed is the diagnosis, not the
frequency. Line 153 already made only the diagnosis claim; this aligns line 88 and
the matching CLI changelog line with it.
The previous wording claimed both SDKs surfaced an invalid dynamic-key
schema only when validation descended into it. That is the CLI's lazy
interpreter; Python never read the keywords, so the defect never surfaced
and the new raise is unconditional at wrap time, not payload-dependent.
Also add @composio/slim to the versions table: it is public and in the
fixed Changesets group with @composio/core, so it ships this fix at the
same minor.
Both SDKs surfaced an invalid dynamic-key schema only when validation
descended into it, so a payload that stayed out of the affected branch
ran against a schema that could not fully validate it. Failing at the
schema is the point, but it is a behavior change and the entry framed it
as affecting only already-failing input.
The open-by-default claim and the Backward Compatibility carve-out both
read as unconditional for objects that name no properties. An explicit
additionalProperties: false closes such an object, and Python raises on
unknown keys it previously dropped through Pydantic's default
extra="ignore" - the same drop-to-raise transition already documented
for objects that declare properties.
The wrap-time paragraph named only Python. The CLI now fails the same way
on the two defects its validator can detect, so both the dynamic-keys
section and Backward Compatibility say which SDK does what, and note that
a reference outside a dynamic-key subschema is still left alone.
@cfworker/json-schema compiles patternProperties keys and resolves $ref
lazily, inside validate. A defect in the schema therefore surfaced once
per call through the same channel as a genuine input failure, telling the
caller their arguments were wrong when the tool's schema was broken.
Check both eagerly, scoped to what the Python SDK rejects while wrapping
a tool: patternProperties keys, and references inside a patternProperties
or schema-valued additionalProperties subschema, followed transitively
through the schemas they resolve to. References elsewhere stay with the
interpreter, so a dangling reference in a branch validation never visits
keeps working.
The filter keeps its catch for the one throw that depends on the instance
rather than the schema - a bigint, symbol, function, or undefined input.
Rethrowing from there would escape Schema.decodeUnknown as a defect
instead of a typed parse error.
The Backward Compatibility carve-out claimed objects declaring no
properties were unaffected. On next, the Python converter read neither
patternProperties nor schema-valued additionalProperties, so those
objects became empty Pydantic models and every dynamic key was dropped
in silence. Those keys are now validated and can raise ValidationError.
Document it as the third call-time behavior change, with before/after,
narrow the carve-out to objects with no dynamic-key constraints, and
note that the prior 'unrelated pattern' behavior was TypeScript-only.
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to next, this PR will
be updated.
# Releases
## @composio/core@0.15.0
### Minor Changes
- 1503786: Replace the loose JSON Schema property type with a recursive,
type-safe definition.
`JSONSchemaProperty` (re-exported from `@composio/core` and reachable
through
`Tool.input_parameters` / `Tool.output_parameters`) is now a concrete
recursive
interface instead of effectively `any`. Runtime behavior is unchanged,
but
consumer code that indexed into it without narrowing (for example
`schema.properties.foo.type` or `schema.default.someField`) may see new
type
errors: `properties` entries are now possibly `undefined` and `default`
/
`enum` values are `unknown`. Narrow with optional chaining or explicit
type
guards when upgrading.
### Patch Changes
- 2ac6ad3: Bound the background npm version check so registry outages
cannot leave the request pending indefinitely.
- 5105612: Match sensitive upload path segments using the target
filesystem's actual case sensitivity so case-insensitive mounts cannot
bypass the denylist without over-blocking distinct paths on
case-sensitive mounts.
- 051c8c5: Redact secrets that appear inside JSON payloads in telemetry
error text. The key/value rule required the separator to follow the key
name directly, so a serialized body such as `{"api_key": "..."}` — the
shape error messages usually carry — was sent unredacted.
- e5c9ada: Refresh the OpenAI runtime dependency to version 7.
- ecd0861: Release unread response bodies on the paths the SDK knowingly
abandons: cancel every intermediate redirect body in `ssrfSafeFetch`,
and the response body before throwing on `!response.ok` in both
URL-upload call sites, instead of leaving them for the garbage collector
to reclaim.
- 2a6a051: Remove the unused internal `isNewerVersion` helper.
- Updated dependencies [1503786]
- @composio/json-schema-to-zod@0.2.2
## @composio/openai@0.11.0
### Minor Changes
- e5c9ada: Add support for OpenAI versions 6 and 7.
## @composio/experimental@0.2.2
### Patch Changes
- 4ceaede: Refresh the TypeBox runtime dependency.
## @composio/json-schema-to-zod@0.2.2
### Patch Changes
- 1503786: Model the parser-supported `min`, `max`, and `example` JSON
Schema extensions in the exported recursive schema type.
## @composio/slim@0.15.0
### Patch Changes
- e5c9ada: Refresh the OpenAI runtime dependency to version 7.
- Updated dependencies [1503786]
- @composio/json-schema-to-zod@0.2.2
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The `openai-zod4-compat` E2E fixture fails on every release branch that
bumps an internal workspace package. It is currently blocking the Python
0.18.2 / TypeScript 0.15.0 train
(https://github.com/ComposioHQ/composio/pull/4038).
## Root cause
The fixture packs `@composio/core` and `@composio/openai` and installs
the tarballs. `pnpm pack` rewrites `@composio/core`'s `workspace:*`
dependency on `@composio/json-schema-to-zod` to the exact workspace
version, so the tarball carries a hard pin:
```json
"@composio/json-schema-to-zod": "0.2.2"
```
On `next` that pin points at an already-published version, so the
install resolves from the registry and the fixture passes. On a release
branch it points at the version about to be published, which does not
exist yet:
```
npm error code ETARGET
npm error notarget No matching version found for @composio/json-schema-to-zod@0.2.2.
```
The setup step then exits non-zero and all 7 assertions fail with empty
output. That is exactly what
https://github.com/ComposioHQ/composio/pull/4038 shows:
`@composio/json-schema-to-zod` is bumped to `0.2.2` there, which is the
first release train to bump an internal dependency of `@composio/core`
since this fixture was added.
## Fix
Pack `@composio/json-schema-to-zod` alongside the other two and install
all three tarballs. npm satisfies core's pin from the top-level tarball,
so the fixture resolves entirely from local builds and no longer depends
on publication order. Behavior on `next` is unchanged — it just prefers
the local build over the identical published one.
## Verification
Reproduced and fixed against the release branch
(`changeset-release/next` @ `8068225d7`, core `0.15.0` /
json-schema-to-zod `0.2.2`) in a local worktree, since Docker was not
available to run the containerized suite here:
- packing core alone and running the fixture's install command
reproduces the ETARGET failure verbatim
- packing all three and installing them succeeds;
`node_modules/@composio/json-schema-to-zod` resolves to `0.2.2` and
`@composio/core`'s pin is satisfied locally
- `node ts/scripts/validate-changesets.mjs` passes; no changeset needed,
the fixture is a private package
CI runs the real containerized suite on this PR.
Prepares the next SDK train: Python `composio` `0.18.2` and the
TypeScript packages that the pending Changesets will publish
(`@composio/core` and `@composio/slim` `0.15.0`, `@composio/openai`
`0.11.0`, `@composio/experimental` and `@composio/json-schema-to-zod`
`0.2.2`).
No CLI release is part of this train.
## What this PR does
- bumps Python core and all 13 provider packages to `0.18.2`, including
`python/composio/__version__.py` and the root plus per-provider
`uv.lock` files
- adds `docs/content/changelog/08-07-26-sdk-releases.mdx`, the combined
customer-facing changelog for both SDKs
- documents `@composio/core` `0.15.0` in that entry so the generated
release PR passes the release-workflow guard
- leaves the pending Changesets untouched; the generated release PR
remains responsible for npm versioning and publication
## Release sequence
1. Merge this PR.
2. Let Changesets regenerate the "Release: update version" PR, wait for
it to go green, then merge it. Merging publishes the TypeScript
packages.
3. Tag the resulting `next` commit `py@0.18.2` to publish the Python
packages to PyPI.
## Notes
- The root `uv.lock` diff carries ~28 lines of marker-annotation churn
beyond the version bumps. That is the repo-pinned `uv` `0.8.19`
normalising markers the committed lockfile had recorded differently; no
dependency versions change.
- `@composio/mastra` and the TypeScript providers are unchanged: their
declared peer ranges still accept `@composio/core` `0.15.0`, so
Changesets does not bump them.
## Verification
- `pnpm test:release-workflow` — passes (Python package, runtime, and
provider versions agree; both SDK versions documented in the changelog)
- `uv run pytest tests/` in `python/` — 925 passed, 35 skipped
- `uv run --package composio python -c "import composio"` — reports
`0.18.2`
- `bun test tests/static/` in `docs/` — 195 pass, 1 pre-existing failure
(`getLLMText — version pointer`, also fails on a clean tree)
- `bun run types:check` in `docs/` — MDX generation and route typegen
succeed; the final `tsc` step could not run locally because
`typescript-7` is absent from `docs/node_modules`