Remove v0 provider (#55177)

Removes the Vercel v0 Provider, as the v0 API has been
depredated/removed (https://api.v0.dev/v1)

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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- agent: Removed Vercel v0 provider as it has been deprecated by Vercel
This commit is contained in:
Bennet Bo Fenner 2026-04-29 12:06:41 +02:00 committed by GitHub
parent 6242777486
commit 2985e058c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1 additions and 656 deletions

11
Cargo.lock generated
View file

@ -9659,7 +9659,6 @@ dependencies = [
"ui",
"ui_input",
"util",
"vercel",
"x_ai",
]
@ -19446,16 +19445,6 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vercel"
version = "0.1.0"
dependencies = [
"anyhow",
"schemars 1.0.4",
"serde",
"strum 0.27.2",
]
[[package]]
name = "version-compare"
version = "0.2.0"

View file

@ -213,7 +213,6 @@ members = [
"crates/ui_prompt",
"crates/util",
"crates/util_macros",
"crates/vercel",
"crates/vim",
"crates/vim_mode_setting",
"crates/watch",
@ -469,7 +468,6 @@ ui_macros = { path = "crates/ui_macros" }
ui_prompt = { path = "crates/ui_prompt" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vercel = { path = "crates/vercel" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
which_key = { path = "crates/which_key" }

View file

@ -1,16 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2639_570)">
<g clip-path="url(#clip1_2639_570)">
<path d="M9.85676 4H13.6675C15.2128 4 16.4654 5.25266 16.4654 6.7979V10.4322H14.9002V6.7979C14.9002 6.76067 14.8988 6.7237 14.8959 6.68706L11.0851 10.4316C11.098 10.432 11.1109 10.4322 11.1238 10.4322H14.9002V11.9105H11.1238C9.57856 11.9105 8.29152 10.6456 8.29152 9.10032V5.47569H9.85676V9.10032C9.85676 9.17012 9.86216 9.23908 9.87264 9.30672L13.7673 5.4798C13.7344 5.47708 13.7012 5.47569 13.6675 5.47569H9.85676V4Z" fill="black"/>
<path d="M6.00752 11.6382L0.5 5.47504H2.71573L5.94924 9.09348V5.47504H7.6014V11.0298C7.6014 11.8682 6.56616 12.2634 6.00752 11.6382Z" fill="black"/>
</g>
</g>
<defs>
<clipPath id="clip0_2639_570">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
</clipPath>
<clipPath id="clip1_2639_570">
<rect width="16" height="8" fill="white" transform="translate(0.5 4)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1,015 B

View file

@ -2352,9 +2352,6 @@
"mistral": {
"api_url": "https://api.mistral.ai/v1",
},
"vercel": {
"api_url": "https://api.v0.dev/v1",
},
"vercel_ai_gateway": {
"api_url": "https://ai-gateway.vercel.sh/v1",
},

View file

@ -24,7 +24,6 @@ pub enum IconName {
AiOpenCode,
AiOpenRouter,
AiVercel,
AiVZero,
AiXAi,
AiZed,
Archive,

View file

@ -61,7 +61,6 @@ tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
ui.workspace = true
ui_input.workspace = true
util.workspace = true
vercel = { workspace = true, features = ["schemars"] }
x_ai = { workspace = true, features = ["schemars"] }
[dev-dependencies]

View file

@ -28,7 +28,6 @@ use crate::provider::open_ai::OpenAiLanguageModelProvider;
use crate::provider::open_ai_compatible::OpenAiCompatibleLanguageModelProvider;
use crate::provider::open_router::OpenRouterLanguageModelProvider;
use crate::provider::opencode::OpenCodeLanguageModelProvider;
use crate::provider::vercel::VercelLanguageModelProvider;
use crate::provider::vercel_ai_gateway::VercelAiGatewayLanguageModelProvider;
use crate::provider::x_ai::XAiLanguageModelProvider;
pub use crate::settings::*;
@ -306,14 +305,6 @@ fn register_language_model_providers(
)),
cx,
);
registry.register_provider(
Arc::new(VercelLanguageModelProvider::new(
client.http_client(),
credentials_provider.clone(),
cx,
)),
cx,
);
registry.register_provider(
Arc::new(VercelAiGatewayLanguageModelProvider::new(
client.http_client(),

View file

@ -12,6 +12,5 @@ pub mod open_ai_compatible;
pub mod open_router;
pub mod opencode;
pub mod vercel;
pub mod vercel_ai_gateway;
pub mod x_ai;

View file

@ -1,470 +0,0 @@
use anyhow::Result;
use collections::BTreeMap;
use credentials_provider::CredentialsProvider;
use futures::{FutureExt, StreamExt, future::BoxFuture};
use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};
use http_client::HttpClient;
use language_model::{
ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,
LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
LanguageModelRequest, LanguageModelToolChoice, RateLimiter, env_var,
};
use open_ai::ResponseStreamEvent;
pub use settings::VercelAvailableModel as AvailableModel;
use settings::{Settings, SettingsStore};
use std::sync::{Arc, LazyLock};
use strum::IntoEnumIterator;
use ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};
use ui_input::InputField;
use util::ResultExt;
use vercel::VERCEL_API_URL;
const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("vercel");
const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("Vercel");
const API_KEY_ENV_VAR_NAME: &str = "VERCEL_API_KEY";
static API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);
#[derive(Clone, Debug, PartialEq)]
pub struct VercelSettings {
pub api_url: String,
pub available_models: Vec<AvailableModel>,
}
pub struct VercelLanguageModelProvider {
http_client: Arc<dyn HttpClient>,
state: Entity<State>,
}
pub struct State {
api_key_state: ApiKeyState,
credentials_provider: Arc<dyn CredentialsProvider>,
}
impl State {
fn is_authenticated(&self) -> bool {
self.api_key_state.has_key()
}
fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {
let credentials_provider = self.credentials_provider.clone();
let api_url = VercelLanguageModelProvider::api_url(cx);
self.api_key_state.store(
api_url,
api_key,
|this| &mut this.api_key_state,
credentials_provider,
cx,
)
}
fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {
let credentials_provider = self.credentials_provider.clone();
let api_url = VercelLanguageModelProvider::api_url(cx);
self.api_key_state.load_if_needed(
api_url,
|this| &mut this.api_key_state,
credentials_provider,
cx,
)
}
}
impl VercelLanguageModelProvider {
pub fn new(
http_client: Arc<dyn HttpClient>,
credentials_provider: Arc<dyn CredentialsProvider>,
cx: &mut App,
) -> Self {
let state = cx.new(|cx| {
cx.observe_global::<SettingsStore>(|this: &mut State, cx| {
let credentials_provider = this.credentials_provider.clone();
let api_url = Self::api_url(cx);
this.api_key_state.handle_url_change(
api_url,
|this| &mut this.api_key_state,
credentials_provider,
cx,
);
cx.notify();
})
.detach();
State {
api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),
credentials_provider,
}
});
Self { http_client, state }
}
fn create_language_model(&self, model: vercel::Model) -> Arc<dyn LanguageModel> {
Arc::new(VercelLanguageModel {
id: LanguageModelId::from(model.id().to_string()),
model,
state: self.state.clone(),
http_client: self.http_client.clone(),
request_limiter: RateLimiter::new(4),
})
}
fn settings(cx: &App) -> &VercelSettings {
&crate::AllLanguageModelSettings::get_global(cx).vercel
}
fn api_url(cx: &App) -> SharedString {
let api_url = &Self::settings(cx).api_url;
if api_url.is_empty() {
VERCEL_API_URL.into()
} else {
SharedString::new(api_url.as_str())
}
}
}
impl LanguageModelProviderState for VercelLanguageModelProvider {
type ObservableEntity = State;
fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {
Some(self.state.clone())
}
}
impl LanguageModelProvider for VercelLanguageModelProvider {
fn id(&self) -> LanguageModelProviderId {
PROVIDER_ID
}
fn name(&self) -> LanguageModelProviderName {
PROVIDER_NAME
}
fn icon(&self) -> IconOrSvg {
IconOrSvg::Icon(IconName::AiVZero)
}
fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {
Some(self.create_language_model(vercel::Model::default()))
}
fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {
Some(self.create_language_model(vercel::Model::default_fast()))
}
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models = BTreeMap::default();
for model in vercel::Model::iter() {
if !matches!(model, vercel::Model::Custom { .. }) {
models.insert(model.id().to_string(), model);
}
}
for model in &Self::settings(cx).available_models {
models.insert(
model.name.clone(),
vercel::Model::Custom {
name: model.name.clone(),
display_name: model.display_name.clone(),
max_tokens: model.max_tokens,
max_output_tokens: model.max_output_tokens,
max_completion_tokens: model.max_completion_tokens,
},
);
}
models
.into_values()
.map(|model| self.create_language_model(model))
.collect()
}
fn is_authenticated(&self, cx: &App) -> bool {
self.state.read(cx).is_authenticated()
}
fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(
&self,
_target_agent: language_model::ConfigurationViewTargetAgent,
window: &mut Window,
cx: &mut App,
) -> AnyView {
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
.into()
}
fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {
self.state
.update(cx, |state, cx| state.set_api_key(None, cx))
}
}
pub struct VercelLanguageModel {
id: LanguageModelId,
model: vercel::Model,
state: Entity<State>,
http_client: Arc<dyn HttpClient>,
request_limiter: RateLimiter,
}
impl VercelLanguageModel {
fn stream_completion(
&self,
request: open_ai::Request,
cx: &AsyncApp,
) -> BoxFuture<'static, Result<futures::stream::BoxStream<'static, Result<ResponseStreamEvent>>>>
{
let http_client = self.http_client.clone();
let (api_key, api_url) = self.state.read_with(cx, |state, cx| {
let api_url = VercelLanguageModelProvider::api_url(cx);
(state.api_key_state.key(&api_url), api_url)
});
let future = self.request_limiter.stream(async move {
let provider = PROVIDER_NAME;
let Some(api_key) = api_key else {
return Err(LanguageModelCompletionError::NoApiKey { provider });
};
let request = open_ai::stream_completion(
http_client.as_ref(),
provider.0.as_str(),
&api_url,
&api_key,
request,
);
let response = request.await?;
Ok(response)
});
async move { Ok(future.await?.boxed()) }.boxed()
}
}
impl LanguageModel for VercelLanguageModel {
fn id(&self) -> LanguageModelId {
self.id.clone()
}
fn name(&self) -> LanguageModelName {
LanguageModelName::from(self.model.display_name().to_string())
}
fn provider_id(&self) -> LanguageModelProviderId {
PROVIDER_ID
}
fn provider_name(&self) -> LanguageModelProviderName {
PROVIDER_NAME
}
fn supports_tools(&self) -> bool {
true
}
fn supports_images(&self) -> bool {
true
}
fn supports_streaming_tools(&self) -> bool {
true
}
fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool {
match choice {
LanguageModelToolChoice::Auto
| LanguageModelToolChoice::Any
| LanguageModelToolChoice::None => true,
}
}
fn telemetry_id(&self) -> String {
format!("vercel/{}", self.model.id())
}
fn max_token_count(&self) -> u64 {
self.model.max_token_count()
}
fn max_output_tokens(&self) -> Option<u64> {
self.model.max_output_tokens()
}
fn stream_completion(
&self,
request: LanguageModelRequest,
cx: &AsyncApp,
) -> BoxFuture<
'static,
Result<
futures::stream::BoxStream<
'static,
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
>,
LanguageModelCompletionError,
>,
> {
let request = crate::provider::open_ai::into_open_ai(
request,
self.model.id(),
self.model.supports_parallel_tool_calls(),
self.model.supports_prompt_cache_key(),
self.max_output_tokens(),
None,
false,
);
let completions = self.stream_completion(request, cx);
async move {
let mapper = crate::provider::open_ai::OpenAiEventMapper::new();
Ok(mapper.map_stream(completions.await?).boxed())
}
.boxed()
}
}
struct ConfigurationView {
api_key_editor: Entity<InputField>,
state: Entity<State>,
load_credentials_task: Option<Task<()>>,
}
impl ConfigurationView {
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let api_key_editor = cx.new(|cx| {
InputField::new(
window,
cx,
"v1:0000000000000000000000000000000000000000000000000",
)
.label("API key")
});
cx.observe(&state, |_, _, cx| {
cx.notify();
})
.detach();
let load_credentials_task = Some(cx.spawn_in(window, {
let state = state.clone();
async move |this, cx| {
if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {
// We don't log an error, because "not signed in" is also an error.
let _ = task.await;
}
this.update(cx, |this, cx| {
this.load_credentials_task = None;
cx.notify();
})
.log_err();
}
}));
Self {
api_key_editor,
state,
load_credentials_task,
}
}
fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();
if api_key.is_empty() {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
.update(cx, |state, cx| state.set_api_key(Some(api_key), cx))
.await
})
.detach_and_log_err(cx);
}
fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.api_key_editor
.update(cx, |input, cx| input.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
.update(cx, |state, cx| state.set_api_key(None, cx))
.await
})
.detach_and_log_err(cx);
}
fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {
!self.state.read(cx).is_authenticated()
}
}
impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();
let configured_card_label = if env_var_set {
format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable")
} else {
let api_url = VercelLanguageModelProvider::api_url(cx);
if api_url == VERCEL_API_URL {
"API key configured".to_string()
} else {
format!("API key configured for {}", api_url)
}
};
let api_key_section = if self.should_render_editor(cx) {
v_flex()
.on_action(cx.listener(Self::save_api_key))
.child(Label::new("To use Zed's agent with Vercel v0, you need to add an API key. Follow these steps:"))
.child(
List::new()
.child(
ListBulletItem::new("")
.child(Label::new("Create one by visiting"))
.child(ButtonLink::new("Vercel v0's console", "https://v0.dev/chat/settings/keys"))
)
.child(
ListBulletItem::new("Paste your API key below and hit enter to start using the agent")
),
)
.child(self.api_key_editor.clone())
.child(
Label::new(format!(
"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed."
))
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Label::new("Note that Vercel v0 is a custom OpenAI-compatible provider.")
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element()
} else {
ConfiguredApiCard::new(configured_card_label)
.disabled(env_var_set)
.when(env_var_set, |this| {
this.tooltip_label(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."))
})
.on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))
.into_any_element()
};
if self.load_credentials_task.is_some() {
div().child(Label::new("Loading credentials…")).into_any()
} else {
v_flex().size_full().child(api_key_section).into_any()
}
}
}

View file

@ -8,8 +8,7 @@ use crate::provider::{
deepseek::DeepSeekSettings, google::GoogleSettings, lmstudio::LmStudioSettings,
mistral::MistralSettings, ollama::OllamaSettings, open_ai::OpenAiSettings,
open_ai_compatible::OpenAiCompatibleSettings, open_router::OpenRouterSettings,
opencode::OpenCodeSettings, vercel::VercelSettings, vercel_ai_gateway::VercelAiGatewaySettings,
x_ai::XAiSettings,
opencode::OpenCodeSettings, vercel_ai_gateway::VercelAiGatewaySettings, x_ai::XAiSettings,
};
#[derive(Debug, RegisterSetting)]
@ -25,7 +24,6 @@ pub struct AllLanguageModelSettings {
pub open_router: OpenRouterSettings,
pub openai: OpenAiSettings,
pub openai_compatible: HashMap<Arc<str>, OpenAiCompatibleSettings>,
pub vercel: VercelSettings,
pub vercel_ai_gateway: VercelAiGatewaySettings,
pub x_ai: XAiSettings,
pub zed_dot_dev: ZedDotDevSettings,
@ -47,7 +45,6 @@ impl settings::Settings for AllLanguageModelSettings {
let open_router = language_models.open_router.unwrap();
let openai = language_models.openai.unwrap();
let openai_compatible = language_models.openai_compatible.unwrap();
let vercel = language_models.vercel.unwrap();
let vercel_ai_gateway = language_models.vercel_ai_gateway.unwrap();
let x_ai = language_models.x_ai.unwrap();
let zed_dot_dev = language_models.zed_dot_dev.unwrap();
@ -115,10 +112,6 @@ impl settings::Settings for AllLanguageModelSettings {
)
})
.collect(),
vercel: VercelSettings {
api_url: vercel.api_url.unwrap(),
available_models: vercel.available_models.unwrap_or_default(),
},
vercel_ai_gateway: VercelAiGatewaySettings {
api_url: vercel_ai_gateway.api_url.unwrap(),
available_models: vercel_ai_gateway.available_models.unwrap_or_default(),

View file

@ -462,7 +462,6 @@ impl JsonSchema for LanguageModelProviderSetting {
"ollama",
"openai",
"openrouter",
"vercel",
"vercel_ai_gateway",
"x_ai",
"zed.dev"

View file

@ -21,7 +21,6 @@ pub struct AllLanguageModelSettingsContent {
pub open_router: Option<OpenRouterSettingsContent>,
pub openai: Option<OpenAiSettingsContent>,
pub openai_compatible: Option<HashMap<Arc<str>, OpenAiCompatibleSettingsContent>>,
pub vercel: Option<VercelSettingsContent>,
pub vercel_ai_gateway: Option<VercelAiGatewaySettingsContent>,
pub x_ai: Option<XAiSettingsContent>,
#[serde(rename = "zed.dev")]
@ -330,23 +329,6 @@ impl Default for OpenAiCompatibleModelCapabilities {
}
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct VercelSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<VercelAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct VercelAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
pub max_completion_tokens: Option<u64>,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct VercelAiGatewaySettingsContent {

View file

@ -1,23 +0,0 @@
[package]
name = "vercel"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/vercel.rs"
test = false
[features]
default = []
schemars = ["dep:schemars"]
[dependencies]
anyhow.workspace = true
schemars = { workspace = true, optional = true }
serde.workspace = true
strum.workspace = true

View file

@ -1 +0,0 @@
../../LICENSE-GPL

View file

@ -1,78 +0,0 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use strum::EnumIter;
pub const VERCEL_API_URL: &str = "https://api.v0.dev/v1";
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
#[default]
#[serde(rename = "v0-1.5-md")]
VZeroOnePointFiveMedium,
#[serde(rename = "custom")]
Custom {
name: String,
/// The name displayed in the UI, such as in the agent panel model dropdown menu.
display_name: Option<String>,
max_tokens: u64,
max_output_tokens: Option<u64>,
max_completion_tokens: Option<u64>,
},
}
impl Model {
pub fn default_fast() -> Self {
Self::VZeroOnePointFiveMedium
}
pub fn from_id(id: &str) -> Result<Self> {
match id {
"v0-1.5-md" => Ok(Self::VZeroOnePointFiveMedium),
invalid_id => anyhow::bail!("invalid model id '{invalid_id}'"),
}
}
pub fn id(&self) -> &str {
match self {
Self::VZeroOnePointFiveMedium => "v0-1.5-md",
Self::Custom { name, .. } => name,
}
}
pub fn display_name(&self) -> &str {
match self {
Self::VZeroOnePointFiveMedium => "v0-1.5-md",
Self::Custom {
name, display_name, ..
} => display_name.as_ref().unwrap_or(name),
}
}
pub fn max_token_count(&self) -> u64 {
match self {
Self::VZeroOnePointFiveMedium => 128_000,
Self::Custom { max_tokens, .. } => *max_tokens,
}
}
pub fn max_output_tokens(&self) -> Option<u64> {
match self {
Self::VZeroOnePointFiveMedium => Some(32_000),
Self::Custom {
max_output_tokens, ..
} => *max_output_tokens,
}
}
pub fn supports_parallel_tool_calls(&self) -> bool {
match self {
Self::VZeroOnePointFiveMedium => true,
Model::Custom { .. } => false,
}
}
pub fn supports_prompt_cache_key(&self) -> bool {
false
}
}

View file

@ -34,7 +34,6 @@ Zed supports these providers with your own API keys:
- [OpenCode](#opencode)
- [OpenRouter](#openrouter)
- [Vercel AI Gateway](#vercel-ai-gateway)
- [Vercel](#vercel-v0)
- [xAI](#xai)
### Amazon Bedrock {#amazon-bedrock}
@ -830,18 +829,6 @@ You can also set a custom endpoint for Vercel AI Gateway in your settings file:
}
```
### Vercel v0 {#vercel-v0}
[Vercel v0](https://v0.app/docs/api/model) is a model for generating full-stack apps, with framework-aware completions for stacks like Next.js and Vercel.
It supports text and image inputs and provides fast streaming responses.
The v0 models are [OpenAI-compatible models](#openai-api-compatible), and Vercel appears as a dedicated provider in the panel's settings view.
To start using it with Zed, ensure you have first created a [v0 API key](https://v0.dev/chat/settings/keys).
Once you have it, paste it directly into the Vercel provider section in the panel's settings view.
You should then find it as `v0-1.5-md` in the model dropdown in the Agent Panel.
### xAI {#xai}
Zed includes a dedicated [xAI](https://x.ai/) provider. You can use your own API key to access Grok models.