Merge branch 'main' into ollama-inline-completions

This commit is contained in:
Oliver Azevedo Barnes 2025-08-20 18:41:08 -04:00
commit 548feed24f
No known key found for this signature in database
757 changed files with 24165 additions and 16916 deletions

View file

@ -104,7 +104,7 @@ fn register_language_model_providers(
cx: &mut Context<LanguageModelRegistry>,
) {
registry.register_provider(
CloudLanguageModelProvider::new(user_store.clone(), client.clone(), cx),
CloudLanguageModelProvider::new(user_store, client.clone(), cx),
cx,
);

View file

@ -15,11 +15,11 @@ use gpui::{
};
use http_client::HttpClient;
use language_model::{
AuthenticateError, LanguageModel, LanguageModelCacheConfiguration,
LanguageModelCompletionError, LanguageModelId, LanguageModelName, LanguageModelProvider,
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent, MessageContent,
RateLimiter, Role,
AuthenticateError, ConfigurationViewTargetAgent, LanguageModel,
LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelId,
LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice,
LanguageModelToolResultContent, MessageContent, RateLimiter, Role,
};
use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason};
use schemars::JsonSchema;
@ -114,7 +114,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.ok();
this.update(cx, |this, cx| {
@ -133,7 +133,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await
.ok();
@ -153,29 +153,14 @@ impl State {
return Task::ready(Ok(()));
}
let credentials_provider = <dyn CredentialsProvider>::global(cx);
let api_url = AllLanguageModelSettings::get_global(cx)
.anthropic
.api_url
.clone();
let key = AnthropicLanguageModelProvider::api_key(cx);
cx.spawn(async move |this, cx| {
let (api_key, from_env) = if let Ok(api_key) = std::env::var(ANTHROPIC_API_KEY_VAR) {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?,
false,
)
};
let key = key.await?;
this.update(cx, |this, cx| {
this.api_key = Some(api_key);
this.api_key_from_env = from_env;
this.api_key = Some(key.key);
this.api_key_from_env = key.from_env;
cx.notify();
})?;
@ -184,6 +169,11 @@ impl State {
}
}
pub struct ApiKey {
pub key: String,
pub from_env: bool,
}
impl AnthropicLanguageModelProvider {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
let state = cx.new(|cx| State {
@ -206,6 +196,33 @@ impl AnthropicLanguageModelProvider {
request_limiter: RateLimiter::new(4),
})
}
pub fn api_key(cx: &mut App) -> Task<Result<ApiKey>> {
let credentials_provider = <dyn CredentialsProvider>::global(cx);
let api_url = AllLanguageModelSettings::get_global(cx)
.anthropic
.api_url
.clone();
if let Ok(key) = std::env::var(ANTHROPIC_API_KEY_VAR) {
Task::ready(Ok(ApiKey {
key,
from_env: true,
}))
} else {
cx.spawn(async move |cx| {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
Ok(ApiKey {
key: String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?,
from_env: false,
})
})
}
}
}
impl LanguageModelProviderState for AnthropicLanguageModelProvider {
@ -299,8 +316,13 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
fn configuration_view(
&self,
target_agent: ConfigurationViewTargetAgent,
window: &mut Window,
cx: &mut App,
) -> AnyView {
cx.new(|cx| ConfigurationView::new(self.state.clone(), target_agent, window, cx))
.into()
}
@ -532,7 +554,7 @@ pub fn into_anthropic(
.into_iter()
.filter_map(|content| match content {
MessageContent::Text(text) => {
let text = if text.chars().last().map_or(false, |c| c.is_whitespace()) {
let text = if text.chars().last().is_some_and(|c| c.is_whitespace()) {
text.trim_end().to_string()
} else {
text
@ -611,11 +633,11 @@ pub fn into_anthropic(
Role::Assistant => anthropic::Role::Assistant,
Role::System => unreachable!("System role should never occur here"),
};
if let Some(last_message) = new_messages.last_mut() {
if last_message.role == anthropic_role {
last_message.content.extend(anthropic_message_content);
continue;
}
if let Some(last_message) = new_messages.last_mut()
&& last_message.role == anthropic_role
{
last_message.content.extend(anthropic_message_content);
continue;
}
// Mark the last segment of the message as cached
@ -791,7 +813,7 @@ impl AnthropicEventMapper {
))];
}
}
return vec![];
vec![]
}
},
Event::ContentBlockStop { index } => {
@ -902,12 +924,18 @@ struct ConfigurationView {
api_key_editor: Entity<Editor>,
state: gpui::Entity<State>,
load_credentials_task: Option<Task<()>>,
target_agent: ConfigurationViewTargetAgent,
}
impl ConfigurationView {
const PLACEHOLDER_TEXT: &'static str = "sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
fn new(
state: gpui::Entity<State>,
target_agent: ConfigurationViewTargetAgent,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
cx.observe(&state, |_, _, cx| {
cx.notify();
})
@ -939,6 +967,7 @@ impl ConfigurationView {
}),
state,
load_credentials_task,
target_agent,
}
}
@ -1012,7 +1041,10 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.on_action(cx.listener(Self::save_api_key))
.child(Label::new("To use Zed's agent with Anthropic, you need to add an API key. Follow these steps:"))
.child(Label::new(format!("To use {}, you need to add an API key. Follow these steps:", match self.target_agent {
ConfigurationViewTargetAgent::ZedAgent => "Zed's agent with Anthropic",
ConfigurationViewTargetAgent::Other(agent) => agent,
})))
.child(
List::new()
.child(
@ -1023,7 +1055,7 @@ impl Render for ConfigurationView {
)
)
.child(
InstructionListItem::text_only("Paste your API key below and hit enter to start using the assistant")
InstructionListItem::text_only("Paste your API key below and hit enter to start using the agent")
)
)
.child(

View file

@ -150,7 +150,7 @@ impl State {
let credentials_provider = <dyn CredentialsProvider>::global(cx);
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(AMAZON_AWS_URL, &cx)
.delete_credentials(AMAZON_AWS_URL, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -174,7 +174,7 @@ impl State {
AMAZON_AWS_URL,
"Bearer",
&serde_json::to_vec(&credentials)?,
&cx,
cx,
)
.await?;
this.update(cx, |this, cx| {
@ -206,7 +206,7 @@ impl State {
(credentials, true)
} else {
let (_, credentials) = credentials_provider
.read_credentials(AMAZON_AWS_URL, &cx)
.read_credentials(AMAZON_AWS_URL, cx)
.await?
.ok_or_else(|| AuthenticateError::CredentialsNotFound)?;
(
@ -348,7 +348,12 @@ impl LanguageModelProvider for BedrockLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
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()
}
@ -407,10 +412,10 @@ impl BedrockModel {
.region(Region::new(region))
.timeout_config(TimeoutConfig::disabled());
if let Some(endpoint_url) = endpoint {
if !endpoint_url.is_empty() {
config_builder = config_builder.endpoint_url(endpoint_url);
}
if let Some(endpoint_url) = endpoint
&& !endpoint_url.is_empty()
{
config_builder = config_builder.endpoint_url(endpoint_url);
}
match auth_method {
@ -460,7 +465,7 @@ impl BedrockModel {
Result<BoxStream<'static, Result<BedrockStreamingResponse, BedrockError>>>,
> {
let Ok(runtime_client) = self
.get_or_init_client(&cx)
.get_or_init_client(cx)
.cloned()
.context("Bedrock client not initialized")
else {
@ -723,11 +728,11 @@ pub fn into_bedrock(
Role::Assistant => bedrock::BedrockRole::Assistant,
Role::System => unreachable!("System role should never occur here"),
};
if let Some(last_message) = new_messages.last_mut() {
if last_message.role == bedrock_role {
last_message.content.extend(bedrock_message_content);
continue;
}
if let Some(last_message) = new_messages.last_mut()
&& last_message.role == bedrock_role
{
last_message.content.extend(bedrock_message_content);
continue;
}
new_messages.push(
BedrockMessage::builder()
@ -912,7 +917,7 @@ pub fn map_to_language_model_completion_events(
Some(ContentBlockDelta::ReasoningContent(thinking)) => match thinking {
ReasoningContentBlockDelta::Text(thoughts) => {
Some(Ok(LanguageModelCompletionEvent::Thinking {
text: thoughts.clone(),
text: thoughts,
signature: None,
}))
}
@ -963,7 +968,7 @@ pub fn map_to_language_model_completion_events(
id: tool_use.id.into(),
name: tool_use.name.into(),
is_input_complete: true,
raw_input: tool_use.input_json.clone(),
raw_input: tool_use.input_json,
input,
},
))
@ -1081,21 +1086,18 @@ impl ConfigurationView {
.access_key_id_editor
.read(cx)
.text(cx)
.to_string()
.trim()
.to_string();
let secret_access_key = self
.secret_access_key_editor
.read(cx)
.text(cx)
.to_string()
.trim()
.to_string();
let session_token = self
.session_token_editor
.read(cx)
.text(cx)
.to_string()
.trim()
.to_string();
let session_token = if session_token.is_empty() {
@ -1103,13 +1105,7 @@ impl ConfigurationView {
} else {
Some(session_token)
};
let region = self
.region_editor
.read(cx)
.text(cx)
.to_string()
.trim()
.to_string();
let region = self.region_editor.read(cx).text(cx).trim().to_string();
let region = if region.is_empty() {
"us-east-1".to_string()
} else {

View file

@ -140,7 +140,7 @@ impl State {
Self {
client: client.clone(),
llm_api_token: LlmApiToken::default(),
user_store: user_store.clone(),
user_store,
status,
accept_terms_of_service_task: None,
models: Vec::new(),
@ -193,7 +193,7 @@ impl State {
fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
let client = self.client.clone();
cx.spawn(async move |state, cx| {
client.sign_in_with_optional_connect(true, &cx).await?;
client.sign_in_with_optional_connect(true, cx).await?;
state.update(cx, |_, cx| cx.notify())
})
}
@ -270,7 +270,7 @@ impl State {
if response.status().is_success() {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
return Ok(serde_json::from_str(&body)?);
Ok(serde_json::from_str(&body)?)
} else {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
@ -307,7 +307,7 @@ impl CloudLanguageModelProvider {
Self {
client,
state: state.clone(),
state,
_maintain_client_status: maintain_client_status,
}
}
@ -320,7 +320,7 @@ impl CloudLanguageModelProvider {
Arc::new(CloudLanguageModel {
id: LanguageModelId(SharedString::from(model.id.0.clone())),
model,
llm_api_token: llm_api_token.clone(),
llm_api_token,
client: self.client.clone(),
request_limiter: RateLimiter::new(4),
})
@ -391,7 +391,12 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
Task::ready(Ok(()))
}
fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView {
fn configuration_view(
&self,
_target_agent: language_model::ConfigurationViewTargetAgent,
_: &mut Window,
cx: &mut App,
) -> AnyView {
cx.new(|_| ConfigurationView::new(self.state.clone()))
.into()
}
@ -592,15 +597,13 @@ impl CloudLanguageModel {
.headers()
.get(SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME)
.and_then(|resource| resource.to_str().ok())
{
if let Some(plan) = response
&& let Some(plan) = response
.headers()
.get(CURRENT_PLAN_HEADER_NAME)
.and_then(|plan| plan.to_str().ok())
.and_then(|plan| cloud_llm_client::Plan::from_str(plan).ok())
{
return Err(anyhow!(ModelRequestLimitReachedError { plan }));
}
{
return Err(anyhow!(ModelRequestLimitReachedError { plan }));
}
} else if status == StatusCode::PAYMENT_REQUIRED {
return Err(anyhow!(PaymentRequiredError));
@ -657,29 +660,29 @@ where
impl From<ApiError> for LanguageModelCompletionError {
fn from(error: ApiError) -> Self {
if let Ok(cloud_error) = serde_json::from_str::<CloudApiError>(&error.body) {
if cloud_error.code.starts_with("upstream_http_") {
let status = if let Some(status) = cloud_error.upstream_status {
status
} else if cloud_error.code.ends_with("_error") {
error.status
} else {
// If there's a status code in the code string (e.g. "upstream_http_429")
// then use that; otherwise, see if the JSON contains a status code.
cloud_error
.code
.strip_prefix("upstream_http_")
.and_then(|code_str| code_str.parse::<u16>().ok())
.and_then(|code| StatusCode::from_u16(code).ok())
.unwrap_or(error.status)
};
if let Ok(cloud_error) = serde_json::from_str::<CloudApiError>(&error.body)
&& cloud_error.code.starts_with("upstream_http_")
{
let status = if let Some(status) = cloud_error.upstream_status {
status
} else if cloud_error.code.ends_with("_error") {
error.status
} else {
// If there's a status code in the code string (e.g. "upstream_http_429")
// then use that; otherwise, see if the JSON contains a status code.
cloud_error
.code
.strip_prefix("upstream_http_")
.and_then(|code_str| code_str.parse::<u16>().ok())
.and_then(|code| StatusCode::from_u16(code).ok())
.unwrap_or(error.status)
};
return LanguageModelCompletionError::UpstreamProviderError {
message: cloud_error.message,
status,
retry_after: cloud_error.retry_after.map(Duration::from_secs_f64),
};
}
return LanguageModelCompletionError::UpstreamProviderError {
message: cloud_error.message,
status,
retry_after: cloud_error.retry_after.map(Duration::from_secs_f64),
};
}
let retry_after = None;

View file

@ -176,7 +176,12 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider {
Task::ready(Err(err.into()))
}
fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView {
fn configuration_view(
&self,
_target_agent: language_model::ConfigurationViewTargetAgent,
_: &mut Window,
cx: &mut App,
) -> AnyView {
let state = self.state.clone();
cx.new(|cx| ConfigurationView::new(state, cx)).into()
}

View file

@ -77,7 +77,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -96,7 +96,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await?;
this.update(cx, |this, cx| {
this.api_key = Some(api_key);
@ -120,7 +120,7 @@ impl State {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
@ -229,7 +229,12 @@ impl LanguageModelProvider for DeepSeekLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
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()
}

View file

@ -12,9 +12,9 @@ use gpui::{
};
use http_client::HttpClient;
use language_model::{
AuthenticateError, LanguageModelCompletionError, LanguageModelCompletionEvent,
LanguageModelToolChoice, LanguageModelToolSchemaFormat, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent, StopReason,
AuthenticateError, ConfigurationViewTargetAgent, LanguageModelCompletionError,
LanguageModelCompletionEvent, LanguageModelToolChoice, LanguageModelToolSchemaFormat,
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, StopReason,
};
use language_model::{
LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider,
@ -37,6 +37,8 @@ use util::ResultExt;
use crate::AllLanguageModelSettings;
use crate::ui::InstructionListItem;
use super::anthropic::ApiKey;
const PROVIDER_ID: LanguageModelProviderId = language_model::GOOGLE_PROVIDER_ID;
const PROVIDER_NAME: LanguageModelProviderName = language_model::GOOGLE_PROVIDER_NAME;
@ -110,7 +112,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -129,7 +131,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await?;
this.update(cx, |this, cx| {
this.api_key = Some(api_key);
@ -156,7 +158,7 @@ impl State {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
@ -198,6 +200,33 @@ impl GoogleLanguageModelProvider {
request_limiter: RateLimiter::new(4),
})
}
pub fn api_key(cx: &mut App) -> Task<Result<ApiKey>> {
let credentials_provider = <dyn CredentialsProvider>::global(cx);
let api_url = AllLanguageModelSettings::get_global(cx)
.google
.api_url
.clone();
if let Ok(key) = std::env::var(GEMINI_API_KEY_VAR) {
Task::ready(Ok(ApiKey {
key,
from_env: true,
}))
} else {
cx.spawn(async move |cx| {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
Ok(ApiKey {
key: String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?,
from_env: false,
})
})
}
}
}
impl LanguageModelProviderState for GoogleLanguageModelProvider {
@ -277,8 +306,13 @@ impl LanguageModelProvider for GoogleLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, 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(), target_agent, window, cx))
.into()
}
@ -382,7 +416,7 @@ impl LanguageModel for GoogleLanguageModel {
cx: &App,
) -> BoxFuture<'static, Result<u64>> {
let model_id = self.model.request_id().to_string();
let request = into_google(request, model_id.clone(), self.model.mode());
let request = into_google(request, model_id, self.model.mode());
let http_client = self.http_client.clone();
let api_key = self.state.read(cx).api_key.clone();
@ -525,7 +559,7 @@ pub fn into_google(
let system_instructions = if request
.messages
.first()
.map_or(false, |msg| matches!(msg.role, Role::System))
.is_some_and(|msg| matches!(msg.role, Role::System))
{
let message = request.messages.remove(0);
Some(SystemInstruction {
@ -572,7 +606,7 @@ pub fn into_google(
top_k: None,
}),
safety_settings: None,
tools: (request.tools.len() > 0).then(|| {
tools: (!request.tools.is_empty()).then(|| {
vec![google_ai::Tool {
function_declarations: request
.tools
@ -771,11 +805,17 @@ fn convert_usage(usage: &UsageMetadata) -> language_model::TokenUsage {
struct ConfigurationView {
api_key_editor: Entity<Editor>,
state: gpui::Entity<State>,
target_agent: language_model::ConfigurationViewTargetAgent,
load_credentials_task: Option<Task<()>>,
}
impl ConfigurationView {
fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
fn new(
state: gpui::Entity<State>,
target_agent: language_model::ConfigurationViewTargetAgent,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
cx.observe(&state, |_, _, cx| {
cx.notify();
})
@ -805,6 +845,7 @@ impl ConfigurationView {
editor.set_placeholder_text("AIzaSy...", cx);
editor
}),
target_agent,
state,
load_credentials_task,
}
@ -880,7 +921,10 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.on_action(cx.listener(Self::save_api_key))
.child(Label::new("To use Zed's agent with Google AI, you need to add an API key. Follow these steps:"))
.child(Label::new(format!("To use {}, you need to add an API key. Follow these steps:", match self.target_agent {
ConfigurationViewTargetAgent::ZedAgent => "Zed's agent with Google AI",
ConfigurationViewTargetAgent::Other(agent) => agent,
})))
.child(
List::new()
.child(InstructionListItem::new(

View file

@ -210,7 +210,7 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider {
.map(|model| {
Arc::new(LmStudioLanguageModel {
id: LanguageModelId::from(model.name.clone()),
model: model.clone(),
model,
http_client: self.http_client.clone(),
request_limiter: RateLimiter::new(4),
}) as Arc<dyn LanguageModel>
@ -226,7 +226,12 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, _window: &mut Window, cx: &mut App) -> AnyView {
fn configuration_view(
&self,
_target_agent: language_model::ConfigurationViewTargetAgent,
_window: &mut Window,
cx: &mut App,
) -> AnyView {
let state = self.state.clone();
cx.new(|cx| ConfigurationView::new(state, cx)).into()
}

View file

@ -76,7 +76,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -95,7 +95,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await?;
this.update(cx, |this, cx| {
this.api_key = Some(api_key);
@ -119,7 +119,7 @@ impl State {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
@ -243,7 +243,12 @@ impl LanguageModelProvider for MistralLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
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()
}

View file

@ -265,7 +265,7 @@ impl LanguageModelProvider for OllamaLanguageModelProvider {
.map(|model| {
Arc::new(OllamaLanguageModel {
id: LanguageModelId::from(model.name.clone()),
model: model.clone(),
model,
http_client: self.http_client.clone(),
request_limiter: RateLimiter::new(4),
}) as Arc<dyn LanguageModel>
@ -283,7 +283,12 @@ impl LanguageModelProvider for OllamaLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
fn configuration_view(
&self,
_target_agent: language_model::ConfigurationViewTargetAgent,
window: &mut Window,
cx: &mut App,
) -> AnyView {
let state = self.state.clone();
cx.new(|cx| ConfigurationView::new(state, window, cx))
.into()

View file

@ -75,7 +75,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -94,7 +94,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -119,7 +119,7 @@ impl State {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
@ -233,7 +233,12 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
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()
}
@ -399,7 +404,7 @@ pub fn into_open_ai(
match content {
MessageContent::Text(text) | MessageContent::Thinking { text, .. } => {
add_message_content_part(
open_ai::MessagePart::Text { text: text },
open_ai::MessagePart::Text { text },
message.role,
&mut messages,
)

View file

@ -38,6 +38,27 @@ pub struct AvailableModel {
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
pub max_completion_tokens: Option<u64>,
#[serde(default)]
pub capabilities: ModelCapabilities,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ModelCapabilities {
pub tools: bool,
pub images: bool,
pub parallel_tool_calls: bool,
pub prompt_cache_key: bool,
}
impl Default for ModelCapabilities {
fn default() -> Self {
Self {
tools: true,
images: false,
parallel_tool_calls: false,
prompt_cache_key: false,
}
}
}
pub struct OpenAiCompatibleLanguageModelProvider {
@ -66,7 +87,7 @@ impl State {
let api_url = self.settings.api_url.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -82,7 +103,7 @@ impl State {
let api_url = self.settings.api_url.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -105,7 +126,7 @@ impl State {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
@ -222,7 +243,12 @@ impl LanguageModelProvider for OpenAiCompatibleLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
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()
}
@ -293,17 +319,17 @@ impl LanguageModel for OpenAiCompatibleLanguageModel {
}
fn supports_tools(&self) -> bool {
true
self.model.capabilities.tools
}
fn supports_images(&self) -> bool {
false
self.model.capabilities.images
}
fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool {
match choice {
LanguageModelToolChoice::Auto => true,
LanguageModelToolChoice::Any => true,
LanguageModelToolChoice::Auto => self.model.capabilities.tools,
LanguageModelToolChoice::Any => self.model.capabilities.tools,
LanguageModelToolChoice::None => true,
}
}
@ -355,13 +381,11 @@ impl LanguageModel for OpenAiCompatibleLanguageModel {
LanguageModelCompletionError,
>,
> {
let supports_parallel_tool_call = true;
let supports_prompt_cache_key = false;
let request = into_open_ai(
request,
&self.model.name,
supports_parallel_tool_call,
supports_prompt_cache_key,
self.model.capabilities.parallel_tool_calls,
self.model.capabilities.prompt_cache_key,
self.max_output_tokens(),
None,
);

View file

@ -112,7 +112,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -131,7 +131,7 @@ impl State {
.clone();
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -157,7 +157,7 @@ impl State {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
@ -306,7 +306,12 @@ impl LanguageModelProvider for OpenRouterLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
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()
}

View file

@ -71,7 +71,7 @@ impl State {
};
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -92,7 +92,7 @@ impl State {
};
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -119,7 +119,7 @@ impl State {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
@ -230,7 +230,12 @@ impl LanguageModelProvider for VercelLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
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()
}

View file

@ -71,7 +71,7 @@ impl State {
};
cx.spawn(async move |this, cx| {
credentials_provider
.delete_credentials(&api_url, &cx)
.delete_credentials(&api_url, cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -92,7 +92,7 @@ impl State {
};
cx.spawn(async move |this, cx| {
credentials_provider
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
.await
.log_err();
this.update(cx, |this, cx| {
@ -119,7 +119,7 @@ impl State {
(api_key, true)
} else {
let (_, api_key) = credentials_provider
.read_credentials(&api_url, &cx)
.read_credentials(&api_url, cx)
.await?
.ok_or(AuthenticateError::CredentialsNotFound)?;
(
@ -230,7 +230,12 @@ impl LanguageModelProvider for XAiLanguageModelProvider {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
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()
}

View file

@ -37,7 +37,7 @@ impl IntoElement for InstructionListItem {
let item_content = if let (Some(button_label), Some(button_link)) =
(self.button_label, self.button_link)
{
let link = button_link.clone();
let link = button_link;
let unique_id = SharedString::from(format!("{}-button", self.label));
h_flex()