Cargo.lock 🔗
@@ -8858,7 +8858,6 @@ dependencies = [
"credentials_provider",
"deepseek",
"editor",
- "extension",
"extension_host",
"fs",
"futures 0.3.31",
Richard Feldman created
Cargo.lock | 1
crates/language_model/src/registry.rs | 5
crates/language_models/Cargo.toml | 1
crates/language_models/src/api_key.rs | 14
crates/language_models/src/extension.rs | 42 -
crates/language_models/src/language_models.rs | 2
crates/language_models/src/provider/cloud.rs | 442 --------------------
crates/language_models/src/provider/google.rs | 18
8 files changed, 7 insertions(+), 518 deletions(-)
@@ -8858,7 +8858,6 @@ dependencies = [
"credentials_provider",
"deepseek",
"editor",
- "extension",
"extension_host",
"fs",
"futures 0.3.31",
@@ -237,11 +237,6 @@ impl LanguageModelRegistry {
}
}
- /// Returns the set of installed LLM extension IDs.
- pub fn installed_llm_extension_ids(&self) -> &HashSet<Arc<str>> {
- &self.installed_llm_extension_ids
- }
-
/// Returns true if a provider should be hidden from the UI.
/// Built-in providers are hidden when their corresponding extension is installed.
pub fn should_hide_provider(&self, provider_id: &LanguageModelProviderId) -> bool {
@@ -28,7 +28,6 @@ convert_case.workspace = true
copilot.workspace = true
credentials_provider.workspace = true
deepseek = { workspace = true, features = ["schemars"] }
-extension.workspace = true
extension_host.workspace = true
fs.workspace = true
futures.workspace = true
@@ -223,10 +223,6 @@ impl ApiKeyState {
}
impl ApiKey {
- pub fn key(&self) -> &str {
- &self.key
- }
-
pub fn from_env(env_var_name: SharedString, key: &str) -> Self {
Self {
source: ApiKeySource::EnvVar(env_var_name),
@@ -234,16 +230,6 @@ impl ApiKey {
}
}
- pub async fn load_from_system_keychain(
- url: &str,
- credentials_provider: &dyn CredentialsProvider,
- cx: &AsyncApp,
- ) -> Result<Self, AuthenticateError> {
- Self::load_from_system_keychain_impl(url, credentials_provider, cx)
- .await
- .into_authenticate_result()
- }
-
async fn load_from_system_keychain_impl(
url: &str,
credentials_provider: &dyn CredentialsProvider,
@@ -1,12 +1,9 @@
use collections::HashMap;
-use extension::{ExtensionLanguageModelProviderProxy, LanguageModelProviderRegistration};
-use gpui::{App, Entity};
-use language_model::{LanguageModelProviderId, LanguageModelRegistry};
-use std::sync::{Arc, LazyLock};
+use std::sync::LazyLock;
/// Maps built-in provider IDs to their corresponding extension IDs.
/// When an extension with this ID is installed, the built-in provider should be hidden.
-pub static BUILTIN_TO_EXTENSION_MAP: LazyLock<HashMap<&'static str, &'static str>> =
+static BUILTIN_TO_EXTENSION_MAP: LazyLock<HashMap<&'static str, &'static str>> =
LazyLock::new(|| {
let mut map = HashMap::default();
map.insert("anthropic", "anthropic");
@@ -21,38 +18,3 @@ pub static BUILTIN_TO_EXTENSION_MAP: LazyLock<HashMap<&'static str, &'static str
pub fn extension_for_builtin_provider(provider_id: &str) -> Option<&'static str> {
BUILTIN_TO_EXTENSION_MAP.get(provider_id).copied()
}
-
-/// Returns true if the given provider ID is a built-in provider that can be hidden by an extension.
-pub fn is_hideable_builtin_provider(provider_id: &str) -> bool {
- BUILTIN_TO_EXTENSION_MAP.contains_key(provider_id)
-}
-
-/// Proxy implementation that registers extension-based language model providers
-/// with the LanguageModelRegistry.
-pub struct ExtensionLanguageModelProxy {
- registry: Entity<LanguageModelRegistry>,
-}
-
-impl ExtensionLanguageModelProxy {
- pub fn new(registry: Entity<LanguageModelRegistry>) -> Self {
- Self { registry }
- }
-}
-
-impl ExtensionLanguageModelProviderProxy for ExtensionLanguageModelProxy {
- fn register_language_model_provider(
- &self,
- provider_id: Arc<str>,
- register_fn: LanguageModelProviderRegistration,
- cx: &mut App,
- ) {
- let _ = provider_id;
- register_fn(cx);
- }
-
- fn unregister_language_model_provider(&self, provider_id: Arc<str>, cx: &mut App) {
- self.registry.update(cx, |registry, cx| {
- registry.unregister_provider(LanguageModelProviderId::from(provider_id), cx);
- });
- }
-}
@@ -14,7 +14,7 @@ pub mod provider;
mod settings;
pub mod ui;
-pub use crate::extension::{extension_for_builtin_provider, is_hideable_builtin_provider};
+pub use crate::extension::extension_for_builtin_provider;
pub use crate::google_ai_api_key::api_key_for_gemini_cli;
use crate::provider::anthropic::AnthropicLanguageModelProvider;
use crate::provider::bedrock::BedrockLanguageModelProvider;
@@ -1,8 +1,5 @@
use ai_onboarding::YoungAccountBanner;
-use anthropic::{
- AnthropicModelMode, ContentDelta, Event, ResponseContent, ToolResultContent, ToolResultPart,
- Usage,
-};
+use anthropic::AnthropicModelMode;
use anyhow::{Context as _, Result, anyhow};
use chrono::{DateTime, Utc};
use client::{Client, ModelRequestUsage, UserStore, zed_urls};
@@ -26,9 +23,8 @@ use language_model::{
LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName,
LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice,
- LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
- LanguageModelToolUseId, LlmApiToken, MessageContent, ModelRequestLimitReachedError,
- PaymentRequiredError, RateLimiter, RefreshLlmTokenListener, Role, StopReason,
+ LanguageModelToolSchemaFormat, LlmApiToken, ModelRequestLimitReachedError,
+ PaymentRequiredError, RateLimiter, RefreshLlmTokenListener,
};
use release_channel::AppVersion;
use schemars::JsonSchema;
@@ -46,6 +42,7 @@ use thiserror::Error;
use ui::{TintColor, prelude::*};
use util::{ResultExt as _, maybe};
+use crate::provider::anthropic::{AnthropicEventMapper, count_anthropic_tokens, into_anthropic};
use crate::provider::google::{GoogleEventMapper, into_google};
use crate::provider::open_ai::{OpenAiEventMapper, count_open_ai_tokens, into_open_ai};
use crate::provider::x_ai::count_xai_tokens;
@@ -1397,434 +1394,3 @@ mod tests {
}
}
}
-
-fn count_anthropic_tokens(
- request: LanguageModelRequest,
- cx: &App,
-) -> BoxFuture<'static, Result<u64>> {
- use gpui::AppContext as _;
- cx.background_spawn(async move {
- let messages = request.messages;
- let mut tokens_from_images = 0;
- let mut string_messages = Vec::with_capacity(messages.len());
-
- for message in messages {
- let mut string_contents = String::new();
-
- for content in message.content {
- match content {
- MessageContent::Text(text) => {
- string_contents.push_str(&text);
- }
- MessageContent::Thinking { .. } => {}
- MessageContent::RedactedThinking(_) => {}
- MessageContent::Image(image) => {
- tokens_from_images += image.estimate_tokens();
- }
- MessageContent::ToolUse(_tool_use) => {}
- MessageContent::ToolResult(tool_result) => match &tool_result.content {
- LanguageModelToolResultContent::Text(text) => {
- string_contents.push_str(text);
- }
- LanguageModelToolResultContent::Image(image) => {
- tokens_from_images += image.estimate_tokens();
- }
- },
- }
- }
-
- if !string_contents.is_empty() {
- string_messages.push(tiktoken_rs::ChatCompletionRequestMessage {
- role: match message.role {
- Role::User => "user".into(),
- Role::Assistant => "assistant".into(),
- Role::System => "system".into(),
- },
- content: Some(string_contents),
- name: None,
- function_call: None,
- });
- }
- }
-
- tiktoken_rs::num_tokens_from_messages("gpt-4", &string_messages)
- .map(|tokens| (tokens + tokens_from_images) as u64)
- })
- .boxed()
-}
-
-fn into_anthropic(
- request: LanguageModelRequest,
- model: String,
- default_temperature: f32,
- max_output_tokens: u64,
- mode: AnthropicModelMode,
-) -> anthropic::Request {
- let mut new_messages: Vec<anthropic::Message> = Vec::new();
- let mut system_message = String::new();
-
- for message in request.messages {
- if message.contents_empty() {
- continue;
- }
-
- match message.role {
- Role::User | Role::Assistant => {
- let mut anthropic_message_content: Vec<anthropic::RequestContent> = message
- .content
- .into_iter()
- .filter_map(|content| match content {
- MessageContent::Text(text) => {
- let text = if text.chars().last().is_some_and(|c| c.is_whitespace()) {
- text.trim_end().to_string()
- } else {
- text
- };
- if !text.is_empty() {
- Some(anthropic::RequestContent::Text {
- text,
- cache_control: None,
- })
- } else {
- None
- }
- }
- MessageContent::Thinking {
- text: thinking,
- signature,
- } => {
- if !thinking.is_empty() {
- Some(anthropic::RequestContent::Thinking {
- thinking,
- signature: signature.unwrap_or_default(),
- cache_control: None,
- })
- } else {
- None
- }
- }
- MessageContent::RedactedThinking(data) => {
- if !data.is_empty() {
- Some(anthropic::RequestContent::RedactedThinking { data })
- } else {
- None
- }
- }
- MessageContent::Image(image) => Some(anthropic::RequestContent::Image {
- source: anthropic::ImageSource {
- source_type: "base64".to_string(),
- media_type: "image/png".to_string(),
- data: image.source.to_string(),
- },
- cache_control: None,
- }),
- MessageContent::ToolUse(tool_use) => {
- Some(anthropic::RequestContent::ToolUse {
- id: tool_use.id.to_string(),
- name: tool_use.name.to_string(),
- input: tool_use.input,
- cache_control: None,
- })
- }
- MessageContent::ToolResult(tool_result) => {
- Some(anthropic::RequestContent::ToolResult {
- tool_use_id: tool_result.tool_use_id.to_string(),
- is_error: tool_result.is_error,
- content: match tool_result.content {
- LanguageModelToolResultContent::Text(text) => {
- ToolResultContent::Plain(text.to_string())
- }
- LanguageModelToolResultContent::Image(image) => {
- ToolResultContent::Multipart(vec![ToolResultPart::Image {
- source: anthropic::ImageSource {
- source_type: "base64".to_string(),
- media_type: "image/png".to_string(),
- data: image.source.to_string(),
- },
- }])
- }
- },
- cache_control: None,
- })
- }
- })
- .collect();
- let anthropic_role = match message.role {
- Role::User => anthropic::Role::User,
- Role::Assistant => anthropic::Role::Assistant,
- Role::System => unreachable!("System role should never occur here"),
- };
- if let Some(last_message) = new_messages.last_mut()
- && last_message.role == anthropic_role
- {
- last_message.content.extend(anthropic_message_content);
- continue;
- }
-
- if message.cache {
- let cache_control_value = Some(anthropic::CacheControl {
- cache_type: anthropic::CacheControlType::Ephemeral,
- });
- for message_content in anthropic_message_content.iter_mut().rev() {
- match message_content {
- anthropic::RequestContent::RedactedThinking { .. } => {}
- anthropic::RequestContent::Text { cache_control, .. }
- | anthropic::RequestContent::Thinking { cache_control, .. }
- | anthropic::RequestContent::Image { cache_control, .. }
- | anthropic::RequestContent::ToolUse { cache_control, .. }
- | anthropic::RequestContent::ToolResult { cache_control, .. } => {
- *cache_control = cache_control_value;
- break;
- }
- }
- }
- }
-
- new_messages.push(anthropic::Message {
- role: anthropic_role,
- content: anthropic_message_content,
- });
- }
- Role::System => {
- if !system_message.is_empty() {
- system_message.push_str("\n\n");
- }
- system_message.push_str(&message.string_contents());
- }
- }
- }
-
- anthropic::Request {
- model,
- messages: new_messages,
- max_tokens: max_output_tokens,
- system: if system_message.is_empty() {
- None
- } else {
- Some(anthropic::StringOrContents::String(system_message))
- },
- thinking: if request.thinking_allowed
- && let AnthropicModelMode::Thinking { budget_tokens } = mode
- {
- Some(anthropic::Thinking::Enabled { budget_tokens })
- } else {
- None
- },
- tools: request
- .tools
- .into_iter()
- .map(|tool| anthropic::Tool {
- name: tool.name,
- description: tool.description,
- input_schema: tool.input_schema,
- })
- .collect(),
- tool_choice: request.tool_choice.map(|choice| match choice {
- LanguageModelToolChoice::Auto => anthropic::ToolChoice::Auto,
- LanguageModelToolChoice::Any => anthropic::ToolChoice::Any,
- LanguageModelToolChoice::None => anthropic::ToolChoice::None,
- }),
- metadata: None,
- stop_sequences: Vec::new(),
- temperature: request.temperature.or(Some(default_temperature)),
- top_k: None,
- top_p: None,
- }
-}
-
-struct AnthropicEventMapper {
- tool_uses_by_index: collections::HashMap<usize, RawToolUse>,
- usage: Usage,
- stop_reason: StopReason,
-}
-
-impl AnthropicEventMapper {
- fn new() -> Self {
- Self {
- tool_uses_by_index: collections::HashMap::default(),
- usage: Usage::default(),
- stop_reason: StopReason::EndTurn,
- }
- }
-
- fn map_event(
- &mut self,
- event: Event,
- ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
- match event {
- Event::ContentBlockStart {
- index,
- content_block,
- } => match content_block {
- ResponseContent::Text { text } => {
- vec![Ok(LanguageModelCompletionEvent::Text(text))]
- }
- ResponseContent::Thinking { thinking } => {
- vec![Ok(LanguageModelCompletionEvent::Thinking {
- text: thinking,
- signature: None,
- })]
- }
- ResponseContent::RedactedThinking { data } => {
- vec![Ok(LanguageModelCompletionEvent::RedactedThinking { data })]
- }
- ResponseContent::ToolUse { id, name, .. } => {
- self.tool_uses_by_index.insert(
- index,
- RawToolUse {
- id,
- name,
- input_json: String::new(),
- },
- );
- Vec::new()
- }
- },
- Event::ContentBlockDelta { index, delta } => match delta {
- ContentDelta::TextDelta { text } => {
- vec![Ok(LanguageModelCompletionEvent::Text(text))]
- }
- ContentDelta::ThinkingDelta { thinking } => {
- vec![Ok(LanguageModelCompletionEvent::Thinking {
- text: thinking,
- signature: None,
- })]
- }
- ContentDelta::SignatureDelta { signature } => {
- vec![Ok(LanguageModelCompletionEvent::Thinking {
- text: "".to_string(),
- signature: Some(signature),
- })]
- }
- ContentDelta::InputJsonDelta { partial_json } => {
- if let Some(tool_use) = self.tool_uses_by_index.get_mut(&index) {
- tool_use.input_json.push_str(&partial_json);
-
- let event = serde_json::from_str::<serde_json::Value>(&tool_use.input_json)
- .ok()
- .and_then(|input| {
- let input_json_roundtripped = serde_json::to_string(&input).ok()?;
-
- if !tool_use.input_json.starts_with(&input_json_roundtripped) {
- return None;
- }
-
- Some(LanguageModelCompletionEvent::ToolUse(
- LanguageModelToolUse {
- id: LanguageModelToolUseId::from(tool_use.id.clone()),
- name: tool_use.name.clone().into(),
- raw_input: tool_use.input_json.clone(),
- input,
- is_input_complete: false,
- thought_signature: None,
- },
- ))
- });
-
- if let Some(event) = event {
- vec![Ok(event)]
- } else {
- Vec::new()
- }
- } else {
- Vec::new()
- }
- }
- },
- Event::ContentBlockStop { index } => {
- if let Some(tool_use) = self.tool_uses_by_index.remove(&index) {
- let event_result = match serde_json::from_str(&tool_use.input_json) {
- Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(
- LanguageModelToolUse {
- id: LanguageModelToolUseId::from(tool_use.id),
- name: tool_use.name.into(),
- raw_input: tool_use.input_json,
- input,
- is_input_complete: true,
- thought_signature: None,
- },
- )),
- Err(json_parse_err) => {
- Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {
- id: LanguageModelToolUseId::from(tool_use.id),
- tool_name: tool_use.name.into(),
- raw_input: tool_use.input_json.into(),
- json_parse_error: json_parse_err.to_string(),
- })
- }
- };
-
- vec![event_result]
- } else {
- Vec::new()
- }
- }
- Event::MessageStart { message } => {
- update_anthropic_usage(&mut self.usage, &message.usage);
- vec![
- Ok(LanguageModelCompletionEvent::UsageUpdate(
- convert_anthropic_usage(&self.usage),
- )),
- Ok(LanguageModelCompletionEvent::StartMessage {
- message_id: message.id,
- }),
- ]
- }
- Event::MessageDelta { delta, usage } => {
- update_anthropic_usage(&mut self.usage, &usage);
- if let Some(stop_reason) = delta.stop_reason.as_deref() {
- self.stop_reason = match stop_reason {
- "end_turn" => StopReason::EndTurn,
- "max_tokens" => StopReason::MaxTokens,
- "tool_use" => StopReason::ToolUse,
- "refusal" => StopReason::Refusal,
- _ => {
- log::error!("Unexpected anthropic stop_reason: {stop_reason}");
- StopReason::EndTurn
- }
- };
- }
- vec![Ok(LanguageModelCompletionEvent::UsageUpdate(
- convert_anthropic_usage(&self.usage),
- ))]
- }
- Event::MessageStop => {
- vec![Ok(LanguageModelCompletionEvent::Stop(self.stop_reason))]
- }
- Event::Error { error } => {
- vec![Err(error.into())]
- }
- _ => Vec::new(),
- }
- }
-}
-
-struct RawToolUse {
- id: String,
- name: String,
- input_json: String,
-}
-
-fn update_anthropic_usage(usage: &mut Usage, new: &Usage) {
- if let Some(input_tokens) = new.input_tokens {
- usage.input_tokens = Some(input_tokens);
- }
- if let Some(output_tokens) = new.output_tokens {
- usage.output_tokens = Some(output_tokens);
- }
- if let Some(cache_creation_input_tokens) = new.cache_creation_input_tokens {
- usage.cache_creation_input_tokens = Some(cache_creation_input_tokens);
- }
- if let Some(cache_read_input_tokens) = new.cache_read_input_tokens {
- usage.cache_read_input_tokens = Some(cache_read_input_tokens);
- }
-}
-
-fn convert_anthropic_usage(usage: &Usage) -> language_model::TokenUsage {
- language_model::TokenUsage {
- input_tokens: usage.input_tokens.unwrap_or(0),
- output_tokens: usage.output_tokens.unwrap_or(0),
- cache_creation_input_tokens: usage.cache_creation_input_tokens.unwrap_or(0),
- cache_read_input_tokens: usage.cache_read_input_tokens.unwrap_or(0),
- }
-}
@@ -1,6 +1,5 @@
use anyhow::{Context as _, Result, anyhow};
use collections::BTreeMap;
-use credentials_provider::CredentialsProvider;
use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture};
use google_ai::{
FunctionDeclaration, GenerateContentResponse, GoogleModelMode, Part, SystemInstruction,
@@ -33,7 +32,6 @@ use ui_input::InputField;
use util::ResultExt;
use zed_env_vars::EnvVar;
-use crate::api_key::ApiKey;
use crate::api_key::ApiKeyState;
use crate::ui::{ConfiguredApiCard, InstructionListItem};
@@ -128,22 +126,6 @@ impl GoogleLanguageModelProvider {
})
}
- pub fn api_key_for_gemini_cli(cx: &mut App) -> Task<Result<String>> {
- if let Some(key) = API_KEY_ENV_VAR.value.clone() {
- return Task::ready(Ok(key));
- }
- let credentials_provider = <dyn CredentialsProvider>::global(cx);
- let api_url = Self::api_url(cx).to_string();
- cx.spawn(async move |cx| {
- Ok(
- ApiKey::load_from_system_keychain(&api_url, credentials_provider.as_ref(), cx)
- .await?
- .key()
- .to_string(),
- )
- })
- }
-
fn settings(cx: &App) -> &GoogleSettings {
&crate::AllLanguageModelSettings::get_global(cx).google
}