Transform JSON schemas for Google AI tools to use `nullable: true`
instead of `type: ["type", "null"]`, which is not supported by the
Gemini API.
Additionally, convert multi-type arrays (e.g., `type: ["string",
"number"]`)
to `anyOf` constraints, as Gemini expects a single string for the `type`
field.
This handles recursive transformation of properties, items, definitions,
and logical operators, safely merging conflicting `anyOf` and `allOf`
constraints.
Closes https://github.com/zed-industries/zed/issues/44875
Closes https://github.com/zed-industries/zed/issues/32429
Release Notes:
- Fixed a bug where using Gemini with certain tools (especially via MCP)
resulted in "Invalid JSON payload received" errors due to incompatible
JSON schema formats.
## Testing
Added unit tests in `crates/language_model/src/tool_schema.rs` covering
nullability, multi-types, and `oneOf` conversions.
### Manual Testing with MCP Test Server
The [MCP Test Server](https://github.com/dastrobu/mcp-test-server) was
used to verify these edge cases with Gemini 3 Flash.
#### Setup
1. Install the test server: `cargo install --git
https://github.com/dastrobu/mcp-test-server`
2. Add to Zed `settings.json`:
```json
"context_servers": [
{
"command": "mcp-test-server"
}
]
```
Use the following pattern in a chat window:
```
call the add_tool function to create a new tool: weather with input schema:
{
"type": "object",
"properties": {
"city": { "type": ["string", "null"] }
}
}
```
Afterwards:
...
```
call it
```
Without fix:
<img width="671" height="448" alt="image"
src="https://github.com/user-attachments/assets/e8b6e7bd-d431-4a7e-8c9a-fff73d82127d"
/>
With fix:
<img width="729" height="334" alt="image"
src="https://github.com/user-attachments/assets/eb758765-4c34-4915-a8cf-dd4bfb993619"
/>
#### Cases verified manually:
**1. Nullability in properties**
- **Input:**
```json
{
"type": "object",
"properties": {
"city": { "type": ["string", "null"] }
}
}
```
- **Converted:**
```json
{
"type": "object",
"properties": {
"city": { "type": "string", "nullable": true }
}
}
```
**2. Multi-type properties**
- **Input:**
```json
{
"type": "object",
"properties": {
"city": { "type": ["string", "number"] }
}
}
```
- **Converted:**
```json
{
"type": "object",
"properties": {
"city": {
"anyOf": [
{ "type": "string" },
{ "type": "number" }
]
}
}
}
```
**3. Explicit `anyOf` with nullability**
- **Input:**
```json
{
"type": "object",
"properties": {
"city": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
]
}
}
}
```
- **Converted:**
```json
{
"type": "object",
"properties": {
"city": {
"anyOf": [
{ "type": "string" },
{ "nullable": true }
]
}
}
}
```
**4. Conflicting `anyOf` sources (Multi-type + existing `anyOf`)**
- **Input:**
```json
{
"type": "object",
"properties": {
"city": {
"type": ["string", "number"],
"anyOf": [
{ "minLength": 5 }
]
}
}
}
```
- **Converted:**
```json
{
"type": "object",
"properties": {
"city": {
"allOf": [
{ "anyOf": [{ "minLength": 5 }] },
{ "anyOf": [{ "type": "string" }, { "type": "number" }] }
]
}
}
}
```
Co-authored-by: Richard Feldman <richard@zed.dev>3 files changedLine totals unavailable