Fix Gemini schema conversion dropping anyOf and nullability#57160
Merged
Conversation
…type schema
Adapts MCP and Rust-defined tool schemas to OpenAPI 3.0's nullability
convention so Gemini accepts them:
- Convert type: [T, "null"] (and type: "null") into nullable: true
alongside the remaining type.
- Convert multi-type arrays like type: ["string", "number"] into
anyOf, combining with any pre-existing anyOf/allOf/oneOf via allOf
without dropping constraints.
- Collapse {nullable: true}-only entries out of anyOf onto the parent
so Option<T>-style schemas land as {type: T, nullable: true}.
- Apply the same nullable conversion in the compile-time
ToJsonSchemaSubsetTransform so Rust-defined tools also keep
nullability on Gemini.
Co-authored-by: Daniel Strobusch <1438302+dastrobu@users.noreply.github.com>
…a-nullability # Conflicts: # crates/language_model_core/src/tool_schema.rs
tomhoule
approved these changes
May 19, 2026
TomPlanche
pushed a commit
to TomPlanche/zed
that referenced
this pull request
May 20, 2026
…stries#57160) Follow-up to zed-industries#49292: - **Bug fix in `push_any_of_constraint`**: when both an `anyOf` and a non-empty `allOf` were already present at the same level, the existing `anyOf` was silently dropped. Now it's always preserved. - **Canonical OpenAPI nullability**: collapse `{nullable: true}`-only entries out of `anyOf` onto the parent so `anyOf: [{type: "string"}, {type: "null"}]` becomes `{type: "string", nullable: true}` (the form Gemini actually expects) instead of `anyOf: [{type: "string"}, {nullable: true}]`. - **Compile-time path consistency**: route `ToJsonSchemaSubsetTransform` through the same `convert_null_in_types_to_nullable` helper so Rust-defined tools using `Option<T>` also keep nullability on Gemini, instead of silently truncating to the non-null type. - Drop an unnecessary clone in `push_any_of_constraint` and simplify `convert_types_to_any_of_defs`. - Add a small `obj()` test helper and a regression test for the `anyOf + allOf + multi-type` case. Release Notes: - Fixed `Option<T>` tool parameters being sent to Gemini without their nullability, and fixed tool schemas with `anyOf` + `allOf` losing constraints during the OpenAPI 3.0 conversion Co-authored-by: Daniel Strobusch <1438302+dastrobu@users.noreply.github.com>
This was referenced May 27, 2026
TomPlanche
pushed a commit
to TomPlanche/zed
that referenced
this pull request
Jun 2, 2026
…stries#57160) Follow-up to zed-industries#49292: - **Bug fix in `push_any_of_constraint`**: when both an `anyOf` and a non-empty `allOf` were already present at the same level, the existing `anyOf` was silently dropped. Now it's always preserved. - **Canonical OpenAPI nullability**: collapse `{nullable: true}`-only entries out of `anyOf` onto the parent so `anyOf: [{type: "string"}, {type: "null"}]` becomes `{type: "string", nullable: true}` (the form Gemini actually expects) instead of `anyOf: [{type: "string"}, {nullable: true}]`. - **Compile-time path consistency**: route `ToJsonSchemaSubsetTransform` through the same `convert_null_in_types_to_nullable` helper so Rust-defined tools using `Option<T>` also keep nullability on Gemini, instead of silently truncating to the non-null type. - Drop an unnecessary clone in `push_any_of_constraint` and simplify `convert_types_to_any_of_defs`. - Add a small `obj()` test helper and a regression test for the `anyOf + allOf + multi-type` case. Release Notes: - Fixed `Option<T>` tool parameters being sent to Gemini without their nullability, and fixed tool schemas with `anyOf` + `allOf` losing constraints during the OpenAPI 3.0 conversion Co-authored-by: Daniel Strobusch <1438302+dastrobu@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #49292:
push_any_of_constraint: when both ananyOfand a non-emptyallOfwere already present at the same level, the existinganyOfwas silently dropped. Now it's always preserved.{nullable: true}-only entries out ofanyOfonto the parent soanyOf: [{type: "string"}, {type: "null"}]becomes{type: "string", nullable: true}(the form Gemini actually expects) instead ofanyOf: [{type: "string"}, {nullable: true}].ToJsonSchemaSubsetTransformthrough the sameconvert_null_in_types_to_nullablehelper so Rust-defined tools usingOption<T>also keep nullability on Gemini, instead of silently truncating to the non-null type.push_any_of_constraintand simplifyconvert_types_to_any_of_defs.obj()test helper and a regression test for theanyOf + allOf + multi-typecase.Release Notes:
Option<T>tool parameters being sent to Gemini without their nullability, and fixed tool schemas withanyOf+allOflosing constraints during the OpenAPI 3.0 conversion