mirror of
https://github.com/zed-industries/zed.git
synced 2026-08-31 11:04:43 +00:00
Zed asked each language model which JSON Schema dialect it wanted, and
rewrote every tool's schema into that dialect before sending it.
`LanguageModelToolSchemaFormat` had grown three variants, and providers
picked between them by model name, so Grok and Gemini took a lossy path
while everything else did not.
The subset exists because Google's `FunctionDeclaration.parameters`
field accepts an OpenAPI-flavored schema rather than JSON Schema, and
`adapt_schema_to_format` tried to translate into it. The translation was
best-effort: constructs it could not represent were left in place and
the function still returned `Ok`. The shape schemars produces for a Rust
`String | Vec<String>` union survives as a nested `anyOf` whose outer
node carries no `type`, and Vertex rejects the entire request over it,
taking every other tool down with it. Where the translation did fail,
the caller logged the error and dropped the tool, so the model silently
lost a capability.
Google now offers `parametersJsonSchema` alongside `parameters`, which
takes plain JSON Schema, and every other provider we ship documents JSON
Schema for function parameters. So the first commit switches
`into_google` to `parametersJsonSchema`, and the second deletes the
dialect: `LanguageModelToolSchemaFormat`,
`LanguageModel::tool_input_format`, `adapt_schema_to_format`, and the
per-model branching in every provider go away, along with
`x_ai::Model::requires_json_schema_subset`.
What remains is `normalize_tool_schema`, which every tool now passes
through. It strips `$schema`, `title`, and `description` from the root,
which no provider reads and which cost tokens; gives an object schema an
explicit `"properties": {}`, which OpenAI requires; and inlines
same-document `$ref`s. That last part is carried over from the deleted
code and matters only for context servers: MCP's Python SDK derives
input schemas through pydantic, which emits `$defs` and `$ref` for any
nested model or enum, and `zod-to-json-schema` emits `definitions` for
reused shapes, while Google rejects references outright. Built-in tools
never contain one, because `root_schema_for` inlines subschemas during
generation. A schema whose references cannot all be resolved is passed
through untouched and the reason logged, so a provider explains what is
wrong instead of receiving something half-rewritten.
## Providers whose behavior changes
| Provider | Models affected | Before | After |
|---|---|---|---|
| Google | all | `JsonSchemaSubset` in `parameters` | full JSON Schema
in `parametersJsonSchema` |
| Zed Cloud | Google, xAI | `JsonSchemaSubset` | full JSON Schema |
| Copilot Chat | Google, xAI, unknown vendors | `JsonSchemaSubset` |
full JSON Schema |
| xAI | Grok 4.3, 4.5, 4.6, 4.20 Reasoning, 4.20 Non-Reasoning |
`JsonSchemaSubset` | full JSON Schema |
| OpenRouter | model ids containing `gemini` or `grok` |
`JsonSchemaSubset` | full JSON Schema |
| Vercel AI Gateway | all | `JsonSchemaSubset` | full JSON Schema |
| OpenAI-compatible | all | `JsonSchemaSubset` | full JSON Schema |
| Bedrock Mantle | all | `JsonSchemaSubset` | full JSON Schema |
| Everything else | — | `JsonSchema` | unchanged apart from
normalization |
One deliberate difference for the last row: the old `JsonSchema` path
also injected `additionalProperties: false` into object roots, and
normalization does not. Nothing depends on it. Serde ignores unknown
fields, so a model inventing an argument never broke deserialization,
and OpenAI's strict function calling — the one feature that requires the
flag — is never enabled: every `ToolDefinition::Function` in the tree
passes `strict: None`. It was also only ever applied on the `JsonSchema`
path, so Google, Grok, Vercel and Bedrock Mantle users never received
it. Dropping it makes behavior uniform rather than removing a guarantee.
## Verification
This was checked against live providers rather than by reasoning about
documentation. A harness in the Delta repository dumps all 24 built-in
tool schemas from this branch and submits them with `tool_choice: none`
to each provider, so the only thing under test is whether the provider
accepts the schemas. Anthropic, OpenAI (chat and responses), Gemini,
xAI, Baseten, OpenRouter (Gemini and Grok), Vercel AI Gateway (Gemini
and Grok), Copilot (Gemini and Grok), the ChatGPT subscription, Bedrock
Converse, Bedrock Mantle (GPT and Grok), and Zed Cloud (Anthropic,
OpenAI, and Google) all accepted them. DeepSeek, Mistral, and OpenCode
were not probed for lack of a key.
The failure this fixes is reproducible on the route that broke. Vertex,
not the Gemini Developer API, is what enforces the constraint: the
harness pins the `grep` declaration production generated, replays it to
Vertex through Zed Cloud in the legacy `parameters` field, and gets back
the exact `INVALID_ARGUMENT` naming `parameters.globs`, while the same
route accepts this branch's schemas through `parametersJsonSchema`. The
Gemini Developer API accepts the broken shape on every model tried, so a
direct API key would never have caught it.
Testing:
- `cargo nextest run -p language_model_core -p google_ai -p agent` for
the schema, `into_google`, MCP and built-in tool tests
- New tests cover reference inlining for the shapes pydantic and zod
produce, recursive references, and unresolvable references
- `built_in_tool_schemas_are_normalized` asserts every built-in tool is
present and normalized
- `crates/agent/examples/dump_tool_schemas.rs` prints the definitions
the harness consumes
- 21 live provider probes with the complete tool set
Release Notes:
- Fixed Gemini rejecting requests because Zed translated tool schemas
into a restricted dialect it could not represent
|
||
|---|---|---|
| .. | ||
| examples | ||
| src | ||
| Cargo.toml | ||
| LICENSE-GPL | ||