# Objective
Add explicit conversation compaction for OpenAI Responses API models,
available through both direct OpenAI connections and Zed Cloud models.
Keep finalized replacement context provider-neutral and separate from
the lifecycle events emitted while automatic compaction is in progress.
## Solution
- Add opt-in explicit compaction support to the `LanguageModel` trait.
- Separate streamed `CompactionUpdate` lifecycle events from finalized
`CompactedContext`.
- Represent provider-native compacted context as an opaque, versioned
state owned by the originating provider.
- Construct compact requests through the same OpenAI request conversion
used for completions, then narrow the request to fields supported by
`/responses/compact`.
- Preserve and validate the complete canonical replacement window
returned by OpenAI.
- Replay the replacement window before messages sent after compaction,
without resending the superseded transcript.
- Support explicit compaction through both the direct OpenAI provider
and `CloudLanguageModel`.
- Update automatic OpenAI and Anthropic compaction to use the same
finalized context representation.
- Reject empty, malformed, incorrectly versioned, and incorrectly owned
provider state.
The corresponding Cloud change adds the `/completions/compact` proxy
route: https://github.com/zed-industries/cloud/pull/3099.
The downstream Delta UI integration is
https://github.com/zed-industries/delta/pull/1739.
## Testing
- `cargo nextest run -p anthropic -p open_ai -p language_models_cloud`
- `cargo check -p language_models --tests`
- `cargo fmt --all --check`
- `git diff --check`
The focused tests cover request construction, canonical-window
preservation and replay, direct and Cloud transports, automatic
compaction lifecycle events, malformed provider state, provider
ownership, format compatibility, and HTTP failure handling.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
---
Release Notes:
- Added: System messages are now sent through the `instructions` field
in OpenAI Responses API requests.
# Objective
Add a new `agent.compaction_model` setting that lets users specify a
separate language model for context compaction (`/compact` and
auto-compaction), independent of the thread's active conversation model.
Compaction is just summarization — there's no reason to pay Opus prices
for it when a cheaper model does the job faster. We've also seen
reasoning models misbehave on this task (empty responses, repetition
loops at high effort), so picking a dedicated non-reasoning model for
compaction is useful.
## Solution
- New `compaction_model: Option<LanguageModelSelection>` field in
`AgentSettingsContent` and `AgentSettings`, mirroring
`thread_summary_model`.
- New `compaction_model: Option<ConfiguredModel>` slot on
`LanguageModelRegistry` with `select_/set_/compaction_model()` trio,
mirroring the existing pattern. The setter deliberately does **not**
emit a registry event in v1; callers read the slot lazily at compaction
time.
- New `Thread::compaction_model(&self, cx: &App)` helper that returns
the configured model or falls back to `self.model()`. Two call sites —
`Thread::compact` and `perform_compaction_if_needed` — now go through
this helper instead of reading `self.model()` directly.
- `build_compaction_telemetry` accepts the compaction model explicitly
so the `model` field reflects the model that actually streamed the
request. `max_tokens` still derives from `thread.model()` (threshold
semantics are unchanged).
- Documentation updated at `docs/src/ai/agent-settings.md` (new
user-facing setting).
**Example:**
```json
{
"agent": {
"default_model": {
"provider": "anthropic",
"model": "claude-opus-4-6"
},
"compaction_model": {
"provider": "anthropic",
"model": "claude-sonnet-4-5"
}
}
}
```
**Resolution chain:**
```
agent.compaction_model (if set & available)
→ thread.model() (always available, current behavior)
```
**Behavior change:**
| Trigger | Before | After |
| --- | --- | --- |
| Manual `/compact` | Uses `thread.model()` | Uses
`agent.compaction_model` if set & available; else `thread.model()` |
| Auto-compaction | Uses `thread.model()` | Same as above |
| `/compact` when thread has no model | `NoModelConfiguredError` |
Succeeds if `compaction_model` resolves |
| `compaction_model` configured but provider missing / model id unknown
| n/a | Falls back to `thread.model()` and logs a one-time warning |
**Explicit non-goals:**
- No `Event::CompactionModelChanged`(no consumer; the `_cx` parameter on
`set_compaction_model` is intentionally accepted for future use).
- No GUI selector (consistent with all other feature-specific models).
- No per-profile override.
- No runtime API-error fallback — only config-time failure (provider not
registered, model id not in `provided_models`) triggers fallback. This
matches every other feature-specific model.
- No change to threshold calculation, auto-compact trigger, or
`COMPACTION_PROMPT`.
- No propagation to subagent threads.
## Testing
3 new unit tests in `crates/agent/src/thread.rs::tests`:
- `test_compaction_uses_configured_compaction_model` — manual `/compact`
routes to the configured model; thread's primary model receives no
request; telemetry reflects the configured model.
- `test_compaction_falls_back_when_compaction_model_unavailable` —
configured-but-unresolvable falls back to `thread.model()`; telemetry
reflects the fallback model.
- `test_auto_compaction_uses_compaction_model` — auto-compaction
triggered by threshold honors the same setting.
All 14 existing compaction tests still pass. Test suites in
`crates/agent_settings`, `crates/language_model`,
`crates/settings_content`, `crates/agent_ui` unchanged. `cargo clippy`
clean on the changed crates.
**How reviewers can test:**
1. Add `agent.compaction_model` to `settings.json` with a cheaper model
than the thread's primary model, run `/compact`, observe the cheaper
model receives the request.
2. Set `agent.compaction_model` to a non-existent provider/model id, run
`/compact`, observe fallback to thread model and a `log::warn!` line.
3. Trigger auto-compaction by reaching the threshold, observe it uses
`compaction_model`.
**Platforms tested:** local Linux (cargo check + cargo test on agent /
agent_settings / language_model / settings_content / agent_ui crates).
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments — no `unsafe`
blocks introduced
- [x] The content adheres to Zed's UI standards — N/A: settings-only
change, no UI touched
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable — model
resolution is an O(1) registry lookup, not on any hot path; no new work
in the streaming loop
Release Notes:
- agent: Add support for specifying which model is used for compaction
(`agent.compaction_model`)
---------
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
This PR improves the onboarding experience when using the agent panel.
Previously we would pick a fallback model in case the provider failed to
authenticate/was slow to resolve models. However, this code had a race
condition (for providers that resolve models dynamically like
Anthropic/GitHub Copilot), since we pick a fallback immediately after
all providers have been authenticated. However, at that point the
configured provider might not have resolved its models, so we would fall
back to a different provider. At some point we added a workaround for
the zed.dev provider.
We landed on a much simpler approach that eliminates the race condition:
We only pick a fallback model in case the user actually has no model
configured in his settings. In case the user has a model configured, we
won't fallback and show an actionable error message:
1. Model is not set and no fallback available
<img width="862" height="78" alt="image"
src="https://github.com/user-attachments/assets/e8e7472a-1c05-4cd2-8efc-49e6d921b0a4"
/>
2. Model is set, but provider is not authenticated
<img width="865" height="65" alt="image"
src="https://github.com/user-attachments/assets/3c8cbf1d-7dd5-4b2e-809a-61e6662721a3"
/>
3. Model is set, provider is authenticated, but model is not in model
list
<img width="863" height="63" alt="image"
src="https://github.com/user-attachments/assets/b97defa2-3fb4-4ec0-b3c6-26878d1c815c"
/>
4. Model is set, but provider is not recognised
<img width="865" height="67" alt="image"
src="https://github.com/user-attachments/assets/ecff1e3f-4f6b-47eb-a25d-125a104baafc"
/>
This plays well with the reason why we have the fallback model in the
first place: We only want to pick a fallback for users that open Zed for
a first time and e.g. have an Anthropic API key present in their
environment. As soon as the user manually changes his provider/model we
won't apply the fallback anymore.
Release Notes:
- agent: Improved error messaging when provider is not configured
- agent: Improve fallback model selection
---------
Co-authored-by: cameron <cameron.studdstreet@gmail.com>
This PR removes/simplifies some APIs that were leftover after moving the
agent settings from the agent panel into the agent settings UI.
Behavior/UI should be identical.
- Removed `ConfigurationViewTargetAgent` since only a single variant was
used
- Removed `configuration_view` and `configuration_view_v2` and replaced
it with `settings_view`
- Removed all the custom configuration views that were replaced by the
API key view abstraction
- Removed `intitial_title` and `initial_description` and moved it to
`ProviderSettingsView`
Release Notes:
- N/A
Closes AI-159
Closes AI-434
Closes AI-435
Release Notes:
- Key agent-related settings now live in the settings editor, close to
all other settings available in Zed. This specifically includes the move
of LLM providers, external agents, and MCP servers to the settings
editor.
---------
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
## Summary
Fixes#37815.
`State::fetch_models()` calls `/api/tags` to list models, then calls
`/api/show` for **every** model in that list to get its capabilities,
collecting the results with `collect::<Result<Vec<_>>>()?`. If
`/api/show` errors for even one model, the whole batch fails and
`fetched_models` is never populated. Since `is_authenticated()` is
defined as `!self.fetched_models.is_empty()`, this means a single bad
model permanently breaks both authentication state and the model picker
for the entire Ollama provider - with no error surfaced anywhere (not
the UI, not the logs), which matches the reports in #37815 of "Connect
does nothing" / "no logs, no nothing".
I hit this myself: I had a stale local reference to an Ollama Cloud
model that had been retired server-side. `/api/show` for that one model
returned `410 Gone`, which silently broke Connect and the model picker
for every other model too. Removing the retired model with `ollama rm`
fixed it immediately, which confirmed the root cause.
## Fix
Instead of aborting the whole fetch on the first error, skip individual
models that fail `/api/show` and log a warning, keeping the rest.
Extracted this into a small `skip_failed_models` helper so it's
unit-testable without mocking HTTP.
## Disclosure
I used Claude (Anthropic's Claude Code) to help track down this root
cause (tracing through `fetch_models`/`is_authenticated` in this file)
and draft the fix + tests below. I reviewed and understand the change -
it's a small, targeted fix to a single function plus two unit tests for
the new helper.
## Test plan
- [x] `cargo check -p language_models` passes
- [x] `cargo test -p language_models --lib ollama::` - all 3 tests pass
(the 2 new ones plus the existing
`test_merge_settings_preserves_display_names_for_similar_models`,
unaffected by this change)
- [x] `cargo fmt -p language_models -- --check` - no diff
## Release Notes
Release Notes:
- Fixed Ollama models silently failing to show up in the model picker
(and "Connect" appearing to do nothing) when a single model's details
couldn't be fetched, e.g. a retired Ollama Cloud model
---------
Co-authored-by: MrSubidubi <finn@zed.dev>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Summary:
- Parse selected model identifiers at the first slash so model IDs may
contain additional slashes.
- Add regression coverage for slash-containing model IDs and invalid
identifiers.
Testing:
- `cargo test -p language_model selected_model_ --lib`
Release Notes:
- Fixed selecting custom language models whose model IDs contain
slashes.
Manage various LLM settings (previously in agent panel) in settings UI.
Behind a feature flag, not staff shipped
Release Notes:
- N/A or Added/Fixed/Improved ...
Adds provider-side context compaction support to the Anthropic and
OpenAI API clients. Client plumbing only; not wired into the UI.
Release Notes:
- N/A
When a Zed Business organization hits its token spend limit, the agent
panel told the user to "check that your API key has access to this
model", and did not display the error message returned by the server.
The message about checking your API key was the generic 401 and 403
error for all language model providers. This is also incorrect in
general since there are no API keys involved in the Zed and ChatGPT
Subscription providers.
So this commit changes the message for authentication errors (401s) to
be provider-specific, with the current message about invalid API key as
the default, and overrides for the providers that don't use API keys.
And the authorization error messages (403s) now include the server error
message. So the spend-limit case now reads "Permission Denied —
Token-based spending limit reached." instead of talking about API keys.
<img width="1888" height="426" alt="grafik"
src="https://github.com/user-attachments/assets/cf9e07cd-66f0-4f8d-828c-79b875edcf2d"
/>
Telemetry event names (`invalid_api_key`, `no_api_key`) are kept
unchanged to avoid breaking existing dashboards.
Release Notes:
- Fixed agent panel errors telling users to check their API key when the
provider doesn't use one (Zed account, ChatGPT subscription). Permission
errors also now show the provider's actual message.
Claude Fable 5 always thinks and cannot honor a request with thinking
disabled, but the cloud models listing gives clients no way to tell it
apart from models where thinking is optional (e.g. Claude Opus 4.6):
both report `supports_thinking: true` plus the same adaptive effort
levels. As a result, the agent panel shows a thinking toggle for Fable
even though turning it off isn't actually supported.
zed-industries/cloud#2789 adds a `supports_disabling_thinking` field to
the models listing. This PR mirrors it through
`cloud_llm_client::LanguageModel` (serde-defaulted to `false`, so a
server without the field is treated as "don't claim thinking can be
turned off") and exposes it as
`LanguageModel::supports_disabling_thinking()`, forwarded from the
listing by `CloudLanguageModel`. The agent panel now hides the thinking
toggle for models that report `false`, showing only the effort selector.
The trait default is `true`: every non-cloud provider in the tree treats
thinking as toggleable today, and only the cloud listing knows about
always-thinking models.
Draft until zed-industries/cloud#2789 lands and deploys.
Release Notes:
- Fixed the agent panel offering a thinking toggle for models that
cannot run with thinking disabled.
Stop the Zed cloud LLM provider from funneling completion failures
through anyhow::Error and collapsing them into
LanguageModelCompletionError::Other (which surfaced as a generic
"Request failed.").
- perform_llm_completion now returns a typed
LanguageModelCompletionError, mapping each failure to its real variant
(SerializeRequest, HttpSend, ApiReadResponseError, and ApiError-derived
status variants).
- response_lines yields a typed ResponseStreamError so mid-stream read/
deserialize failures become ApiReadResponseError/DeserializeResponse
without a runtime downcast.
- Add a first-class PaymentRequired variant for HTTP 402 and remove the
now-dead PaymentRequiredError struct and its anyhow downcast checks.
Self-Review Checklist:
- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- N/A
---------
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
<img width="325" height="201" alt="Screenshot 2026-06-09 at 1 38 32 PM"
src="https://github.com/user-attachments/assets/a6518073-1e17-41ff-a8fc-cb279fcd4436"
/>
Adds support for Anthropic's Claude Fable 5 model when using your own
Anthropic API key. Because Fable 5 cannot be offered under Zero Data
Retention (Anthropic retains inference logs for 30 days), this gates the
model behind an explicit data-retention consent: a new
telemetry.anthropic_retention setting (default off, surfaced in the
Privacy section of the settings UI), and a hard, non-retryable check in
the cloud completion path that raises a typed error when consent is
missing.
When Fable 5 declines a request, it transparently falls back to Claude
Opus 4.8 (matching Anthropic's server-side behavior), and the agent
panel shows a callout for the consent error with "Switch to Opus 4.8" /
"Accept" actions that resume the failed turn so the user's message
continues without retyping.
Closes AI-382
Release Notes:
- Add Claude Fable 5 to Anthropic BYOK
---------
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
It look like this:
<img width="1698" height="688" alt="grafik"
src="https://github.com/user-attachments/assets/02a37271-63d3-42da-887f-e17b31e8d9ca"
/>
The idea is to avoid people turning on fast mode without understanding
the financial implications. It also clarifiers (in the BYOK case) why
they might not see a difference between fast mode enabled and disabled.
Release Notes:
- N/A
---------
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- N/A
Drop the `count_tokens` API and related implementations across
providers, and remove the unused `tiktoken-rs` dependency.
I was going to update the dependency becuase they finally released a fix
we needed. But then I realized we only used this api in one place, the
Rules library. And for most models it would have been wildly incorrect
becuase we use tiktoken, i.e. OpenAI tokenizers, for almost every model,
which is going to give incorrect results.
Given that, I just removed these because the difference in how we get
these has caused plenty of confusion in the past.
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
When images are resized to meet provider size constraints (Anthropic's
1568px limit or the 5MB encoded-PNG cap), the stored ImageSize was still
recording the original width/height rather than the final post-downscale
dimensions. This caused incorrect token estimation via estimate_tokens()
since it uses width * height / 750.
Use processed_image.dimensions() after all downscale passes so that
ImageSize reflects the actual image sent to the provider.
Release Notes:
- Fixed an issue where token estimation would be incorrect in case where
the thread contained downscaled images.
Reimplements #36722 while fixing the race that required the revert in
#36932.
When no default model is configured, this picks an environment fallback
by authenticating all providers. It always prefers the Zed cloud
provider when it's authenticated, and waits for its models to load
before picking another provider as the fallback, so we don't flicker
from Zed models to Anthropic while sign-in is in flight.
The fallback is recomputed whenever provider state changes (via
`ProviderStateChanged`/`AddedProvider`/`RemovedProvider` events), so the
selection becomes correct as soon as cloud models arrive.
### What changed vs. the original PR
- `language_models::init` now owns `authenticate_all_providers`
(previously done in `LanguageModelPickerDelegate` and `agent`'s
`LanguageModels`).
- After all authentications settle, and on any subsequent provider state
change, `update_environment_fallback_model` recomputes the fallback.
- The fallback logic prefers Zed cloud: if the cloud provider is
authenticated, only use it (waiting for its models to load). Otherwise,
fall through to the first authenticated provider with a default or
recommended model.
- `LanguageModelRegistry::default_model()` falls back to
`environment_fallback_model` when no explicit default is set.
- Existing `Thread`s that are empty are updated to the new default when
`DefaultModelChanged` fires, so a blank thread started before sign-in
switches to Zed models once the user signs in.
Release Notes:
- agent: Automatically select a model when there's no selected model or configured default
- `language_model` no longer depends on provider-specific crates such as
`anthropic` and `open_ai` (inverted dependency)
- `language_model_core` was extracted from `language_model` which
contains the types for the provider-specific crates to convert to/from.
- `gpui::SharedString` has been extracted into its own crate (still
exposed by `gpui`), so `language_model_core` and provider API crates
don't have to depend on `gpui`.
- Removes some unnecessary `&'static str` | `SharedString` -> `String`
-> `SharedString` conversions across the codebase.
- Extracts the core logic of the cloud `LanguageModelProvider` into its
own crate with simpler dependencies.
Release Notes:
- N/A
---------
Co-authored-by: John Tur <john-tur@outlook.com>
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- N/A
This PR decouples `language_model`'s dependence on Zed-specific
implementation details. In particular
* `credentials_provider` is split into a generic `credentials_provider`
crate that provides a trait, and `zed_credentials_provider` that
implements the said trait for Zed-specific providers and has functions
that can populate a global state with them
* `zed_env_vars` is split into a generic `env_var` crate that provides
generic tooling for managing env vars, and `zed_env_vars` that contains
Zed-specific statics
* `client` is now dependent on `language_model` and not vice versa
Release Notes:
- N/A
A couple of things that this PR wants to accomplish:
* remove dependency on `settings` crate from `language_model`
* refactor provider-specific code into submodules - to be honest, I
would go one step further and put all provider-specific bits in
`language_models` instead but I realise we have cloud logic in
`language_model` which uses those too making it tricky
* move anthropic-specific telemetry into `language_models` crate - I
think it makes more sense for it to be there
Anyhow, I would very appreciate if you could have a look @mikayla-maki
and @maxdeviant and lemme know what you think, if you would tweak
something, etc.
Release Notes:
- N/A
🫡
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- Removed legacy Text Threads feature to help streamline the new agentic
workflows in Zed. Thanks to all of you who were enthusiastic Text Thread
users over the years ❤️!
---------
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Turns out we were including the description of a tool inside the schema
again, which I don't think is needed.
Before:
```
LanguageModelRequestTool {
name: "web_search",
description: "Search the web for information using your query.\nUse this when you need real-time information, facts, or data that might not be in your training.\nResults will include snippets and links from relevant web pages.",
input_schema: Object {
"required": Array [
String("query"),
],
"description": String("Search the web for information using your query.\nUse this when you need real-time information, facts, or data that might not be in your training.\nResults will include snippets and links from relevant web pages."),
"type": String("object"),
"properties": Object {
"query": Object {
"description": String("The search term or question to query on the web."),
"type": String("string"),
},
},
"additionalProperties": Bool(false),
},
use_input_streaming: false,
},
```
After:
```
LanguageModelRequestTool {
name: "web_search",
description: "Search the web for information using your query.\nUse this when you need real-time information, facts, or data that might not be in your training.\nResults will include snippets and links from relevant web pages.",
input_schema: Object {
"required": Array [
String("query"),
],
"type": String("object"),
"properties": Object {
"query": Object {
"description": String("The search term or question to query on the web."),
"type": String("string"),
},
},
"additionalProperties": Bool(false),
},
use_input_streaming: false,
},
```
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes#45315
Release Notes:
- agent: Reduced amount of tokens consumed by tool descriptions
### Summary
The Gemini API enforces strict validation on `function_declarations` and
rejects requests containing unsupported JSON Schema keywords such as
`additionalProperties`, `propertyNames`. This caused Write mode to fail
with "failed to stream completion" when tools with complex schemas were
used.
This PR strips these unsupported keywords from tool schemas before
sending them to the Gemini API in `adapt_to_json_schema_subset`.
### How to Review
- Check `crates/language_model/src/tool_schema.rs` — the
`adapt_to_json_schema_subset` function now removes
`additionalProperties` and `propertyNames` from schemas.
- Tests are added covering removal of these keys and nested schema
handling.
- To reproduce the original issue, send a tool schema containing
`propertyNames` or `additionalProperties` to the Gemini API — it returns
HTTP 400 `INVALID_ARGUMENT`
### How to Test
Run the unit tests:
```sh
cargo test -p language_model
```
OR manually reproduce this using ->
```
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=YOUR_KEY" \
-H 'Content-Type: application/json' \
-d '{"contents":[{"parts":[{"text":"test"}]}],"tools":[{"functionDeclarations":[{"name":"test","parameters":{"type":"OBJECT","properties":{"field":{"type":"OBJECT","propertyNames":{"pattern":"^[a-z]+$"},"additionalProperties":{"type":"STRING"}}}}}]}]}'
```
#### Closes#52430
- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Video
[Screencast from 2026-03-29
08-32-18.webm](https://github.com/user-attachments/assets/a0069f0e-1f2b-45dc-85bf-f24aacb08599)
### Note : Reopens previous work from closed PR #52644 (fork was
deleted)
Release Notes:
- Fixed an issue where Gemini models would not work when using specific
MCP servers
## Context
| Eval | Score |
|------|-------|
| eval_delete_function | 1.00 |
| eval_extract_handle_command_output | 0.96 |
| eval_translate_doc_comments | 0.96 |
Porting the rest of the evals is still a todo.
## Self-Review Checklist
<!-- Check before requesting review: -->
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
---------
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This PR moves the `CompletionIntent` enum from the `cloud_llm_client`
crate to the `language_model` crate, as it is no longer part of the
Cloud interface.
Release Notes:
- N/A
## Context
I was getting some leak detection failures in evals and tracked it down
to these entities getting passed into observe/subscribe callbacks and
causing cycles.
Release Notes:
- N/A
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
When we switch organizations, we try and refresh the token. If the token
refresh fails, we are left with the old LlmApiToken, which is for the
wrong organization. In this commit, we make sure to clear the old token
before trying a refresh on organization switch.
Release Notes:
- N/A
---------
Co-authored-by: Neel <neel@zed.dev>
We were sending the raw tool debug output as JSON to the model rather
than whatever the tool intended as content for the model.
Which meant we were sending unneeded information to the model, which
matters in the edit tool case.
Release Notes:
- N/A
The edit prediction, web search and completions endpoints in Cloud all
use tokens called LlmApiToken. These were independently created, cached,
and refreshed in three places: the cloud language model provider, the
edit prediction store, and the cloud web search provider. Each held its
own LlmApiToken instance, meaning three separate requests to get these
tokens at startup / login and three redundant refreshes whenever the
server signaled a token update was needed.
We already had a global singleton reacting to the refresh signals:
RefreshLlmTokenListener. It now holds a single LlmApiToken that all
three services use, performs the refresh itself, and emits
RefreshLlmTokenEvent only after the token is fresh. That event is used
by the language model provider to re-fetch models after a refresh. The
singleton is accessed only through `LlmApiToken::global()`.
I have tested this manually, and it token acquisition and usage appear
to be working fine.
Edit: I've tested it with a long running session, and refresh seems to
be working fine too.
Release Notes:
- N/A
---------
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Emit client-side organization changed events through
`RefreshLlmTokenListener` so it produces the same `RefreshLlmTokenEvent`
used for server-pushed `UserUpdated` messages.
This keeps token refresh fan-out in one place.
Closes CLO-383.
Release Notes:
- N/A
---------
Co-authored-by: Tom Houlé <tom@tomhoule.com>
This is already expected on the cloud side. This lets us know under
which organization the user is logged in when requesting an llm_api
token.
Closes CLO-337
Release Notes:
- N/A
This is a staff only toggle for now, since the consequences of
activating it are not obvious and quite dire (tokens costs 6 times
more).
Also, persist thinking, thinking effort and fast mode in DbThread so the
thinking mode toggle and thinking effort are persisted.
Release Notes:
- Agent: The thinking mode toggle and thinking effort are now persisted
when selecting a thread from history.
Before you mark this PR as ready for review, make sure that you have:
- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [x] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
Release Notes:
- N/A
---------
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
This PR makes it so the user gets signed out upon receiving an
Unauthorized response when acquiring an LLM token.
This is a re-landing of #49661.
Closes CLO-324.
Release Notes:
- N/A
### Description
Related Discussions: #44499, #35742, #31851
Display cost multiplier for GitHub Copilot models in the model selectors
(Both in Chat Panel and Inline Assistant)
<img width="436" height="800" alt="image"
src="https://github.com/user-attachments/assets/c9ebd8fa-4d55-4be8-b3e1-f46dbf1f0145"
/>
### Some technical notes
Although this PR's primary intent is to show the cost multiplier for
GitHub Copilot models alone, I have included some necessary plumbing to
allow specifying costs for other providers in future. I have introduced
an enum called `LanguageModelCostInfo` for showing cost in different
ways for different models. Now, this enum is used in `LanguageModel`
trait to get the cost info.
For now to begin with, in `LanguageModelCostInfo`, I have specified two
ways of pricing: Request-based (1 Agent request - GitHub Copilot uses
this) and Token-based (1M Input tokens / 1M Output tokens). I had
initially thought about adding a `Free` type, especially for Ollama but
didn't do it after realizing that Ollama has paid plans. Right now, only
the Request-based pricing is implemented and used for Copilot models.
Feel free to suggest changes on how to improve this design better.
Release Notes:
- Show cost multiplier for GitHub Copilot models
---------
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Add StreamEnded variant so the client can distinguish between a stream
that the cloud ran to completion versus one that was interrupted (see
CLO-258). **That logic is to be added in a follow up PR**.
Add an Unknown fallback with #[serde(other)] for forward-compatible
deserialization of future variants.
The client advertises support via a new
x-zed-client-supports-stream-ended-request-completion-status header. The
server will only send the new variant if that header is passed. Both
StreamEnded and Unknown are silently ignored at the event mapping layer
(from_completion_request_status returns Ok(None)).
Part of CLO-264 and CLO-266; cloud-side changes to follow.
Release Notes:
- N/A
---------
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
TODO:
- [x] Review code
- [x] Decide whether to keep ignored API tests
Release Notes:
- Fixed a bug where cancelling a thread mid-thought would cause further
anthropic requests to fail
- Fixed a bug where the model configured on a thread would not be
persisted alongside that thread
This PR updates the model selector to highlight the latest models that
are available through the Zed provider:
<img width="388" height="477" alt="Screenshot 2026-02-06 at 1 46 41 PM"
src="https://github.com/user-attachments/assets/70760399-ecf6-46e3-80a7-cb998216c192"
/>
Closes CLO-205.
Release Notes:
- Added a "Latest" indicator to highlight the latest models available
through the Zed provider.