Files

1 line
749 KiB
JSON

{"repo": "alex-keagel/caduceus-zed", "files": {"crates/language_models/src/provider/deepseek.rs": {"4cc7e687e20525ddc477de79ff4d1d6590fcaf48": {"t": 1785745868.4707713, "n": 24678, "h": "4dcd8be7fe63fcde", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthAction, AuthenticateError, EnvVar, IconOrSvg, LanguageModel,\n LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName,\n LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,\n LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice,\n LanguageModelToolResultContent, LanguageModelToolUse, MessageContent, ProviderAuthState,\n RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse language_model::util::{fix_streamed_json, parse_tool_arguments};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n credentials_provider: Arc<dyn CredentialsProvider>,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.store(\n api_url,\n api_key,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(\n http_client: Arc<dyn HttpClient>,\n credentials_provider: Arc<dyn CredentialsProvider>,\n cx: &mut App,\n ) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let credentials_provider = this.credentials_provider.clone();\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n credentials_provider,\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn auth_state(&self, cx: &App) -> ProviderAuthState {\n if self.state.read(cx).is_authenticated() {\n ProviderAuthState::Authenticated\n } else {\n ProviderAuthState::NotAuthenticated {\n action: AuthAction::EnterApiKeyInSettings,\n }\n }\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_streaming_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => {\n let should_add = if message.role == Role::User {\n !text.trim().is_empty()\n } else {\n !text.is_empty()\n };\n\n if should_add {\n messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n });\n }\n }\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone()\n && !content.is_empty()\n {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n\n if !entry.id.is_empty() && !entry.name.is_empty() {\n if let Ok(input) = serde_json::from_str::<serde_json::Value>(\n &fix_streamed_json(&entry.arguments),\n ) {\n events.push(Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: entry.id.clone().into(),\n name: entry.name.as_str().into(),\n is_input_complete: false,\n input,\n raw_input: entry.arguments.clone(),\n thought_signature: None,\n },\n )));\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "b3aeb4ffd4e16631f8af70a56566c8adcbf5d2f5": {"t": 1785757416.8169415, "n": 24678, "h": "4dcd8be7fe63fcde", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthAction, AuthenticateError, EnvVar, IconOrSvg, LanguageModel,\n LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName,\n LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,\n LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice,\n LanguageModelToolResultContent, LanguageModelToolUse, MessageContent, ProviderAuthState,\n RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse language_model::util::{fix_streamed_json, parse_tool_arguments};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n credentials_provider: Arc<dyn CredentialsProvider>,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.store(\n api_url,\n api_key,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(\n http_client: Arc<dyn HttpClient>,\n credentials_provider: Arc<dyn CredentialsProvider>,\n cx: &mut App,\n ) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let credentials_provider = this.credentials_provider.clone();\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n credentials_provider,\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn auth_state(&self, cx: &App) -> ProviderAuthState {\n if self.state.read(cx).is_authenticated() {\n ProviderAuthState::Authenticated\n } else {\n ProviderAuthState::NotAuthenticated {\n action: AuthAction::EnterApiKeyInSettings,\n }\n }\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_streaming_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => {\n let should_add = if message.role == Role::User {\n !text.trim().is_empty()\n } else {\n !text.is_empty()\n };\n\n if should_add {\n messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n });\n }\n }\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone()\n && !content.is_empty()\n {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n\n if !entry.id.is_empty() && !entry.name.is_empty() {\n if let Ok(input) = serde_json::from_str::<serde_json::Value>(\n &fix_streamed_json(&entry.arguments),\n ) {\n events.push(Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: entry.id.clone().into(),\n name: entry.name.as_str().into(),\n is_input_complete: false,\n input,\n raw_input: entry.arguments.clone(),\n thought_signature: None,\n },\n )));\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "4aebd93f36000ef5d31a4f10a1d47935bd28d8c7": {"t": 1785757418.1474783, "n": 24678, "h": "b96e385083105750", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n AuthAction, ProviderAuthState,\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse language_model::util::{fix_streamed_json, parse_tool_arguments};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n credentials_provider: Arc<dyn CredentialsProvider>,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.store(\n api_url,\n api_key,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(\n http_client: Arc<dyn HttpClient>,\n credentials_provider: Arc<dyn CredentialsProvider>,\n cx: &mut App,\n ) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let credentials_provider = this.credentials_provider.clone();\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n credentials_provider,\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn auth_state(&self, cx: &App) -> ProviderAuthState {\n if self.state.read(cx).is_authenticated() {\n ProviderAuthState::Authenticated\n } else {\n ProviderAuthState::NotAuthenticated {\n action: AuthAction::EnterApiKeyInSettings,\n }\n }\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_streaming_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => {\n let should_add = if message.role == Role::User {\n !text.trim().is_empty()\n } else {\n !text.is_empty()\n };\n\n if should_add {\n messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n });\n }\n }\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone()\n && !content.is_empty()\n {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n\n if !entry.id.is_empty() && !entry.name.is_empty() {\n if let Ok(input) = serde_json::from_str::<serde_json::Value>(\n &fix_streamed_json(&entry.arguments),\n ) {\n events.push(Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: entry.id.clone().into(),\n name: entry.name.as_str().into(),\n is_input_complete: false,\n input,\n raw_input: entry.arguments.clone(),\n thought_signature: None,\n },\n )));\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "98c17ca1607a9bb223f831af6e221b3e7d47b28c": {"t": 1785757419.5256855, "n": 24436, "h": "e9053196876e1bef", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse language_model::util::{fix_streamed_json, parse_tool_arguments};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n credentials_provider: Arc<dyn CredentialsProvider>,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.store(\n api_url,\n api_key,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(\n http_client: Arc<dyn HttpClient>,\n credentials_provider: Arc<dyn CredentialsProvider>,\n cx: &mut App,\n ) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let credentials_provider = this.credentials_provider.clone();\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n credentials_provider,\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_streaming_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => {\n let should_add = if message.role == Role::User {\n !text.trim().is_empty()\n } else {\n !text.is_empty()\n };\n\n if should_add {\n messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n });\n }\n }\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone()\n && !content.is_empty()\n {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n\n if !entry.id.is_empty() && !entry.name.is_empty() {\n if let Ok(input) = serde_json::from_str::<serde_json::Value>(\n &fix_streamed_json(&entry.arguments),\n ) {\n events.push(Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: entry.id.clone().into(),\n name: entry.name.as_str().into(),\n is_input_complete: false,\n input,\n raw_input: entry.arguments.clone(),\n thought_signature: None,\n },\n )));\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "29609d3599c10fbec40a3756cfeac41d5e04e57e": {"t": 1785757421.2372413, "n": 24437, "h": "a39255dc14ba945d", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse crate::provider::util::{fix_streamed_json, parse_tool_arguments};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n credentials_provider: Arc<dyn CredentialsProvider>,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.store(\n api_url,\n api_key,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let credentials_provider = self.credentials_provider.clone();\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(\n http_client: Arc<dyn HttpClient>,\n credentials_provider: Arc<dyn CredentialsProvider>,\n cx: &mut App,\n ) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let credentials_provider = this.credentials_provider.clone();\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n |this| &mut this.api_key_state,\n credentials_provider,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n credentials_provider,\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_streaming_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => {\n let should_add = if message.role == Role::User {\n !text.trim().is_empty()\n } else {\n !text.is_empty()\n };\n\n if should_add {\n messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n });\n }\n }\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone()\n && !content.is_empty()\n {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n\n if !entry.id.is_empty() && !entry.name.is_empty() {\n if let Ok(input) = serde_json::from_str::<serde_json::Value>(\n &fix_streamed_json(&entry.arguments),\n ) {\n events.push(Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: entry.id.clone().into(),\n name: entry.name.as_str().into(),\n is_input_complete: false,\n input,\n raw_input: entry.arguments.clone(),\n thought_signature: None,\n },\n )));\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "62cea4e6b03336b4bb974b0268fe94f643bc8c62": {"t": 1785757422.956713, "n": 23747, "h": "a849ed1602eca608", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse crate::provider::util::{fix_streamed_json, parse_tool_arguments};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_streaming_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => {\n let should_add = if message.role == Role::User {\n !text.trim().is_empty()\n } else {\n !text.is_empty()\n };\n\n if should_add {\n messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n });\n }\n }\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone()\n && !content.is_empty()\n {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n\n if !entry.id.is_empty() && !entry.name.is_empty() {\n if let Ok(input) = serde_json::from_str::<serde_json::Value>(\n &fix_streamed_json(&entry.arguments),\n ) {\n events.push(Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: entry.id.clone().into(),\n name: entry.name.as_str().into(),\n is_input_complete: false,\n input,\n raw_input: entry.arguments.clone(),\n thought_signature: None,\n },\n )));\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "fdf144fb7292e23d4360afbb2b1729a2549a700b": {"t": 1785757424.7147303, "n": 23327, "h": "972e4b653ff20949", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse crate::provider::util::{fix_streamed_json, parse_tool_arguments};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_streaming_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n\n if !entry.id.is_empty() && !entry.name.is_empty() {\n if let Ok(input) = serde_json::from_str::<serde_json::Value>(\n &fix_streamed_json(&entry.arguments),\n ) {\n events.push(Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: entry.id.clone().into(),\n name: entry.name.as_str().into(),\n is_input_complete: false,\n input,\n raw_input: entry.arguments.clone(),\n thought_signature: None,\n },\n )));\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "87bc2aac5cc99e8425e1c29c1af6bb7bc15e280f": {"t": 1785757426.5071454, "n": 23317, "h": "80ad634300ea26a4", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse crate::provider::util::parse_tool_arguments;\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_streaming_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n\n if !entry.id.is_empty() && !entry.name.is_empty() {\n if let Ok(input) = serde_json::from_str::<serde_json::Value>(\n &partial_json_fixer::fix_json(&entry.arguments),\n ) {\n events.push(Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: entry.id.clone().into(),\n name: entry.name.as_str().into(),\n is_input_complete: false,\n input,\n raw_input: entry.arguments.clone(),\n thought_signature: None,\n },\n )));\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "22c16b690a1575f6b9eae7fbe7e6b0575afb17a9": {"t": 1785757427.920869, "n": 22422, "h": "05e4abc20874303c", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nuse crate::provider::util::parse_tool_arguments;\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match parse_tool_arguments(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "29cf14ed2fc72c36d4502120daad09ded049ea6e": {"t": 1785757429.4193273, "n": 22402, "h": "c1802fb306c893f2", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "21050e2d372b358357c62ee242c16c454b0a2813": {"t": 1785757430.9770195, "n": 22619, "h": "3a93469cd9591d52", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n bypass_rate_limit: bool,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream_with_bypass(\n async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n },\n bypass_rate_limit,\n );\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let bypass_rate_limit = request.bypass_rate_limit;\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, bypass_rate_limit, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "97c35c084b9a1058b2a57ada953a20eb8af95665": {"t": 1785757432.6828585, "n": 22402, "h": "c1802fb306c893f2", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let (api_key, api_url) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n });\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = Some(state.update(cx, |state, cx| state.authenticate(cx))) {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "f90fe5ce9e8ab515c372a6122be9d17e3fe8c912": {"t": 1785757433.9618728, "n": 22572, "h": "7cb15bd683a72a69", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "6055b45ee19ab61ff3b00498872ef6f31e748f37": {"t": 1785757435.382058, "n": 22546, "h": "61bbf57e47a4a08b", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconOrSvg {\n IconOrSvg::Icon(IconName::AiDeepSeek)\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "0283bfb04949295086b5ce6c892defa9c3ecc008": {"t": 1785757436.770054, "n": 22517, "h": "627ec136c73a6d75", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n ApiKeyState, AuthenticateError, EnvVar, LanguageModel, LanguageModelCompletionError,\n LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,\n LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,\n LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,\n LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .load_if_needed(api_url, |this| &mut this.api_key_state, cx)\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state\n .handle_url_change(api_url, |this| &mut this.api_key_state, cx);\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(\n ListBulletItem::new(\"\")\n .child(Label::new(\"Get your API key from the\"))\n .child(ButtonLink::new(\n \"DeepSeek console\",\n \"https://platform.deepseek.com/api_keys\",\n )),\n )\n .child(ListBulletItem::new(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "5dd8561b06c8af4ee46f3aa8bcf839f208b8c7bf": {"t": 1785757438.0679169, "n": 22570, "h": "82ca7e631f6643d6", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{List, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::ui::ConfiguredApiCard;\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx)),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = model == &deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n let mut current_reasoning: Option<String> = None;\n\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n reasoning_content: current_reasoning.take(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { text, .. } => {\n // Accumulate reasoning content for next assistant message\n current_reasoning.get_or_insert_default().push_str(&text);\n }\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n reasoning_content: current_reasoning.take(),\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "c0fadae8813669e0166ab3fa524f1b43559b4d53": {"t": 1785757439.479218, "n": 22192, "h": "7177273b8cc66b49", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{List, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::ui::ConfiguredApiCard;\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx)),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n thought_signature: None,\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "2fb3d593bc8f9608f0041c8c6183214be7425d5d": {"t": 1785757441.0528617, "n": 22135, "h": "f04abff6c2394156", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{List, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::ui::ConfiguredApiCard;\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx)),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n let configured_card_label = if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n };\n\n if self.load_credentials_task.is_some() {\n div()\n .child(Label::new(\"Loading credentials...\"))\n .into_any_element()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any_element()\n } else {\n ConfiguredApiCard::new(configured_card_label)\n .disabled(env_var_set)\n .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))\n .into_any_element()\n }\n }\n}\n"}, "cc1d66b530655da654310682bcd8d34293ffda6f": {"t": 1785757442.8280025, "n": 23399, "h": "c795cf42b387e8f8", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{Icon, IconName, List, prelude::*};\nuse ui_input::InputField;\nuse util::ResultExt;\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx)),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .flex_1()\n .min_w_0()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(div().w_full().overflow_x_hidden().text_ellipsis().child(\n Label::new(if env_var_set {\n format!(\n \"API key set in {API_KEY_ENV_VAR_NAME} environment variable\"\n )\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\"API key configured for {}\", api_url)\n }\n }),\n )),\n )\n .child(\n h_flex().flex_shrink_0().child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "8f3da5c5cd9cc14694cdb8b1da4fac17b47b64eb": {"t": 1785757444.1718633, "n": 23159, "h": "d8f5c0da442a1c9a", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{Icon, IconName, List, prelude::*};\nuse ui_input::InputField;\nuse util::{ResultExt, truncate_and_trailoff};\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx)),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<InputField>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| InputField::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\n \"API key configured for {}\",\n truncate_and_trailoff(&api_url, 32)\n )\n }\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "67984d5e496e1920adbf283482a8b878a6b5816c": {"t": 1785757445.8213646, "n": 23174, "h": "9cd2580f2845314c", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\n\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\n\nuse ui::{Icon, IconName, List, prelude::*};\nuse ui_input::SingleLineInput;\nuse util::{ResultExt, truncate_and_trailoff};\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx)),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<SingleLineInput>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor =\n cx.new(|cx| SingleLineInput::new(window, cx, \"sk-00000000000000000000000000000000\"));\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(self.api_key_editor.clone())\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\n \"API key configured for {}\",\n truncate_and_trailoff(&api_url, 32)\n )\n }\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "fcdab160f964c6ddaa2e59a5547467d82bc2c1ad": {"t": 1785757447.3923848, "n": 24857, "h": "3ee29d9f22daafae", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace,\n Window,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\npub use settings::DeepseekAvailableModel as AvailableModel;\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::{ResultExt, truncate_and_trailoff};\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx)),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", window, cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\n \"API key configured for {}\",\n truncate_and_trailoff(&api_url, 32)\n )\n }\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "a598fbaa735da17489ff9883162c21028942a7b1": {"t": 1785757449.0330176, "n": 25088, "h": "2b8bcb7f82bc624d", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse deepseek::DEEPSEEK_API_URL;\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace,\n Window,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::{ResultExt, truncate_and_trailoff};\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = Self::api_url(cx);\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(Self::api_url(cx)),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> SharedString {\n let api_url = &Self::settings(cx).api_url;\n if api_url.is_empty() {\n DEEPSEEK_API_URL.into()\n } else {\n SharedString::new(api_url.as_str())\n }\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(&api_url), api_url)\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", window, cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME} environment variable\")\n } else {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n if api_url == DEEPSEEK_API_URL {\n \"API key configured\".to_string()\n } else {\n format!(\n \"API key configured for {}\",\n truncate_and_trailoff(&api_url, 32)\n )\n }\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "634ae72cadae10ce83127c090f4903ddf87681a0": {"t": 1785757450.4953918, "n": 24501, "h": "edf75577f02bb8be", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace,\n Window,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::ResultExt;\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::{api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = SharedString::new(DeepSeekLanguageModelProvider::api_url(cx));\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = SharedString::new(DeepSeekLanguageModelProvider::api_url(cx));\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = SharedString::new(Self::api_url(cx));\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(SharedString::new(Self::api_url(cx))),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &crate::AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> &str {\n &Self::settings(cx).api_url\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n (state.api_key_state.key(api_url), api_url.to_string())\n }) else {\n return future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", window, cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME}\")\n } else {\n \"API key configured\".to_string()\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "98edf1bf0b6e1b6ec62986d370763920dde8b890": {"t": 1785757451.7930949, "n": 24671, "h": "43fe290e87603da8", "c": "use anyhow::{Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace,\n Window,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::{Arc, LazyLock};\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::ResultExt;\nuse zed_env_vars::{EnvVar, env_var};\n\nuse crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\n\nconst API_KEY_ENV_VAR_NAME: &str = \"DEEPSEEK_API_KEY\";\nstatic API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key_state: ApiKeyState,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key_state.has_key()\n }\n\n fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {\n let api_url = SharedString::new(DeepSeekLanguageModelProvider::api_url(cx));\n self.api_key_state\n .store(api_url, api_key, |this| &mut this.api_key_state, cx)\n }\n\n fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n let api_url = SharedString::new(DeepSeekLanguageModelProvider::api_url(cx));\n self.api_key_state.load_if_needed(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n )\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| {\n cx.observe_global::<SettingsStore>(|this: &mut State, cx| {\n let api_url = SharedString::new(Self::api_url(cx));\n this.api_key_state.handle_url_change(\n api_url,\n &API_KEY_ENV_VAR,\n |this| &mut this.api_key_state,\n cx,\n );\n cx.notify();\n })\n .detach();\n State {\n api_key_state: ApiKeyState::new(SharedString::new(Self::api_url(cx))),\n }\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n })\n }\n\n fn settings(cx: &App) -> &DeepSeekSettings {\n &AllLanguageModelSettings::get_global(cx).deepseek\n }\n\n fn api_url(cx: &App) -> &str {\n &Self::settings(cx).api_url\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in &Self::settings(cx).available_models {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state\n .update(cx, |state, cx| state.set_api_key(None, cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n\n let api_key_and_url = self.state.read_with(cx, |state, cx| {\n let api_url = DeepSeekLanguageModelProvider::api_url(cx);\n let api_key = state.api_key_state.key(api_url);\n (api_key, api_url.to_string())\n });\n let (api_key, api_url) = match api_key_and_url {\n Ok(api_key_and_url) => api_key_and_url,\n Err(err) => {\n return futures::future::ready(Err(err)).boxed();\n }\n };\n\n let future = self.request_limiter.stream(async move {\n let Some(api_key) = api_key else {\n return Err(LanguageModelCompletionError::NoApiKey {\n provider: PROVIDER_NAME,\n });\n };\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", window, cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx).trim().to_string();\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(None, cx))?\n .await\n })\n .detach_and_log_err(cx);\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {API_KEY_ENV_VAR_NAME} environment variable.\"\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {API_KEY_ENV_VAR_NAME}\")\n } else {\n \"API key configured\".to_string()\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "acb3406eb883929a28317356d6296ff5b1621091": {"t": 1785757453.3672357, "n": 25682, "h": "b389100a38df2791", "c": "use anyhow::{Context as _, Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, AppContext as _, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle,\n WhiteSpace,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::Arc;\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::ResultExt;\n\nuse crate::{AllLanguageModelSettings, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\nconst DEEPSEEK_API_KEY_VAR: &str = \"DEEPSEEK_API_KEY\";\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key: Option<String>,\n api_key_from_env: bool,\n _subscription: Subscription,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key.is_some()\n }\n\n fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .delete_credentials(&api_url, cx)\n .await\n .log_err();\n this.update(cx, |this, cx| {\n this.api_key = None;\n this.api_key_from_env = false;\n cx.notify();\n })\n })\n }\n\n fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .write_credentials(&api_url, \"Bearer\", api_key.as_bytes(), cx)\n .await?;\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n cx.notify();\n })\n })\n }\n\n fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n if self.is_authenticated() {\n return Task::ready(Ok(()));\n }\n\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_API_KEY_VAR) {\n (api_key, true)\n } else {\n let (_, api_key) = credentials_provider\n .read_credentials(&api_url, cx)\n .await?\n .ok_or(AuthenticateError::CredentialsNotFound)?;\n (\n String::from_utf8(api_key).context(\"invalid {PROVIDER_NAME} API key\")?,\n false,\n )\n };\n\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n this.api_key_from_env = from_env;\n cx.notify();\n })?;\n\n Ok(())\n })\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| State {\n api_key: None,\n api_key_from_env: false,\n _subscription: cx.observe_global::<SettingsStore>(|_this: &mut State, cx| {\n cx.notify();\n }),\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n }) as Arc<dyn LanguageModel>\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in AllLanguageModelSettings::get_global(cx)\n .deepseek\n .available_models\n .iter()\n {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state.update(cx, |state, cx| state.reset_api_key(cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| {\n let settings = &AllLanguageModelSettings::get_global(cx).deepseek;\n (state.api_key.clone(), settings.api_url.clone())\n }) else {\n return futures::future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let api_key = api_key.context(\"Missing DeepSeek API Key\")?;\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", window, cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx);\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(api_key, cx))?\n .await\n })\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| state.update(cx, |state, cx| state.reset_api_key(cx))?.await)\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_from_env;\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {} environment variable.\",\n DEEPSEEK_API_KEY_VAR\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {}\", DEEPSEEK_API_KEY_VAR)\n } else {\n \"API key configured\".to_string()\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "9e0e233319d06956bb28fb0609bb843e89d1a812": {"t": 1785757455.108874, "n": 25674, "h": "43d20ac0d0d34c40", "c": "use anyhow::{Context as _, Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, AppContext as _, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle,\n WhiteSpace,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::Arc;\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::ResultExt;\n\nuse crate::{AllLanguageModelSettings, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\nconst DEEPSEEK_API_KEY_VAR: &str = \"DEEPSEEK_API_KEY\";\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key: Option<String>,\n api_key_from_env: bool,\n _subscription: Subscription,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key.is_some()\n }\n\n fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .delete_credentials(&api_url, cx)\n .await\n .log_err();\n this.update(cx, |this, cx| {\n this.api_key = None;\n this.api_key_from_env = false;\n cx.notify();\n })\n })\n }\n\n fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .write_credentials(&api_url, \"Bearer\", api_key.as_bytes(), cx)\n .await?;\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n cx.notify();\n })\n })\n }\n\n fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n if self.is_authenticated() {\n return Task::ready(Ok(()));\n }\n\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_API_KEY_VAR) {\n (api_key, true)\n } else {\n let (_, api_key) = credentials_provider\n .read_credentials(&api_url, cx)\n .await?\n .ok_or(AuthenticateError::CredentialsNotFound)?;\n (\n String::from_utf8(api_key).context(\"invalid {PROVIDER_NAME} API key\")?,\n false,\n )\n };\n\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n this.api_key_from_env = from_env;\n cx.notify();\n })?;\n\n Ok(())\n })\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| State {\n api_key: None,\n api_key_from_env: false,\n _subscription: cx.observe_global::<SettingsStore>(|_this: &mut State, cx| {\n cx.notify();\n }),\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n }) as Arc<dyn LanguageModel>\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in AllLanguageModelSettings::get_global(cx)\n .deepseek\n .available_models\n .iter()\n {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state.update(cx, |state, cx| state.reset_api_key(cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| {\n let settings = &AllLanguageModelSettings::get_global(cx).deepseek;\n (state.api_key.clone(), settings.api_url.clone())\n }) else {\n return futures::future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let api_key = api_key.context(\"Missing DeepSeek API Key\")?;\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx);\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(api_key, cx))?\n .await\n })\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| state.update(cx, |state, cx| state.reset_api_key(cx))?.await)\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_from_env;\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {} environment variable.\",\n DEEPSEEK_API_KEY_VAR\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {}\", DEEPSEEK_API_KEY_VAR)\n } else {\n \"API key configured\".to_string()\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "8b89ea1a801af6190b1b6e6557a69fadb08db93f": {"t": 1785757456.5082598, "n": 25677, "h": "020e9dfd6f8720ab", "c": "use anyhow::{Context as _, Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, AppContext as _, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle,\n WhiteSpace,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::Arc;\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::ResultExt;\n\nuse crate::{AllLanguageModelSettings, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\nconst DEEPSEEK_API_KEY_VAR: &str = \"DEEPSEEK_API_KEY\";\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key: Option<String>,\n api_key_from_env: bool,\n _subscription: Subscription,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key.is_some()\n }\n\n fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .delete_credentials(&api_url, &cx)\n .await\n .log_err();\n this.update(cx, |this, cx| {\n this.api_key = None;\n this.api_key_from_env = false;\n cx.notify();\n })\n })\n }\n\n fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .write_credentials(&api_url, \"Bearer\", api_key.as_bytes(), &cx)\n .await?;\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n cx.notify();\n })\n })\n }\n\n fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n if self.is_authenticated() {\n return Task::ready(Ok(()));\n }\n\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_API_KEY_VAR) {\n (api_key, true)\n } else {\n let (_, api_key) = credentials_provider\n .read_credentials(&api_url, &cx)\n .await?\n .ok_or(AuthenticateError::CredentialsNotFound)?;\n (\n String::from_utf8(api_key).context(\"invalid {PROVIDER_NAME} API key\")?,\n false,\n )\n };\n\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n this.api_key_from_env = from_env;\n cx.notify();\n })?;\n\n Ok(())\n })\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| State {\n api_key: None,\n api_key_from_env: false,\n _subscription: cx.observe_global::<SettingsStore>(|_this: &mut State, cx| {\n cx.notify();\n }),\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n }) as Arc<dyn LanguageModel>\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in AllLanguageModelSettings::get_global(cx)\n .deepseek\n .available_models\n .iter()\n {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(\n &self,\n _target_agent: language_model::ConfigurationViewTargetAgent,\n window: &mut Window,\n cx: &mut App,\n ) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state.update(cx, |state, cx| state.reset_api_key(cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| {\n let settings = &AllLanguageModelSettings::get_global(cx).deepseek;\n (state.api_key.clone(), settings.api_url.clone())\n }) else {\n return futures::future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let api_key = api_key.context(\"Missing DeepSeek API Key\")?;\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx);\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(api_key, cx))?\n .await\n })\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| state.update(cx, |state, cx| state.reset_api_key(cx))?.await)\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_from_env;\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {} environment variable.\",\n DEEPSEEK_API_KEY_VAR\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {}\", DEEPSEEK_API_KEY_VAR)\n } else {\n \"API key configured\".to_string()\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "d497f52e17a05959c5ddb298143a3dd60b7c40f1": {"t": 1785757458.1371734, "n": 25577, "h": "2222f057faa50c54", "c": "use anyhow::{Context as _, Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, AppContext as _, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle,\n WhiteSpace,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::Arc;\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::ResultExt;\n\nuse crate::{AllLanguageModelSettings, ui::InstructionListItem};\n\nconst PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new(\"deepseek\");\nconst PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new(\"DeepSeek\");\nconst DEEPSEEK_API_KEY_VAR: &str = \"DEEPSEEK_API_KEY\";\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key: Option<String>,\n api_key_from_env: bool,\n _subscription: Subscription,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key.is_some()\n }\n\n fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .delete_credentials(&api_url, &cx)\n .await\n .log_err();\n this.update(cx, |this, cx| {\n this.api_key = None;\n this.api_key_from_env = false;\n cx.notify();\n })\n })\n }\n\n fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .write_credentials(&api_url, \"Bearer\", api_key.as_bytes(), &cx)\n .await?;\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n cx.notify();\n })\n })\n }\n\n fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n if self.is_authenticated() {\n return Task::ready(Ok(()));\n }\n\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_API_KEY_VAR) {\n (api_key, true)\n } else {\n let (_, api_key) = credentials_provider\n .read_credentials(&api_url, &cx)\n .await?\n .ok_or(AuthenticateError::CredentialsNotFound)?;\n (\n String::from_utf8(api_key).context(\"invalid {PROVIDER_NAME} API key\")?,\n false,\n )\n };\n\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n this.api_key_from_env = from_env;\n cx.notify();\n })?;\n\n Ok(())\n })\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| State {\n api_key: None,\n api_key_from_env: false,\n _subscription: cx.observe_global::<SettingsStore>(|_this: &mut State, cx| {\n cx.notify();\n }),\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n }) as Arc<dyn LanguageModel>\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in AllLanguageModelSettings::get_global(cx)\n .deepseek\n .available_models\n .iter()\n {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state.update(cx, |state, cx| state.reset_api_key(cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| {\n let settings = &AllLanguageModelSettings::get_global(cx).deepseek;\n (state.api_key.clone(), settings.api_url.clone())\n }) else {\n return futures::future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let api_key = api_key.context(\"Missing DeepSeek API Key\")?;\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n PROVIDER_ID\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n PROVIDER_NAME\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::from(error))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::from(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {\n id: tool_call.id.clone().into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx);\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(api_key, cx))?\n .await\n })\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| state.update(cx, |state, cx| state.reset_api_key(cx))?.await)\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_from_env;\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {} environment variable.\",\n DEEPSEEK_API_KEY_VAR\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {}\", DEEPSEEK_API_KEY_VAR)\n } else {\n \"API key configured\".to_string()\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "108162423da6d5d37bd78bffed74e7ebc2e8a83b": {"t": 1785757459.7427313, "n": 25602, "h": "a7b62e4ad885f76f", "c": "use anyhow::{Context as _, Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, AppContext as _, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle,\n WhiteSpace,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason, TokenUsage,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::Arc;\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::ResultExt;\n\nuse crate::{AllLanguageModelSettings, ui::InstructionListItem};\n\nconst PROVIDER_ID: &str = \"deepseek\";\nconst PROVIDER_NAME: &str = \"DeepSeek\";\nconst DEEPSEEK_API_KEY_VAR: &str = \"DEEPSEEK_API_KEY\";\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key: Option<String>,\n api_key_from_env: bool,\n _subscription: Subscription,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key.is_some()\n }\n\n fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .delete_credentials(&api_url, &cx)\n .await\n .log_err();\n this.update(cx, |this, cx| {\n this.api_key = None;\n this.api_key_from_env = false;\n cx.notify();\n })\n })\n }\n\n fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .write_credentials(&api_url, \"Bearer\", api_key.as_bytes(), &cx)\n .await?;\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n cx.notify();\n })\n })\n }\n\n fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n if self.is_authenticated() {\n return Task::ready(Ok(()));\n }\n\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_API_KEY_VAR) {\n (api_key, true)\n } else {\n let (_, api_key) = credentials_provider\n .read_credentials(&api_url, &cx)\n .await?\n .ok_or(AuthenticateError::CredentialsNotFound)?;\n (\n String::from_utf8(api_key).context(\"invalid {PROVIDER_NAME} API key\")?,\n false,\n )\n };\n\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n this.api_key_from_env = from_env;\n cx.notify();\n })?;\n\n Ok(())\n })\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| State {\n api_key: None,\n api_key_from_env: false,\n _subscription: cx.observe_global::<SettingsStore>(|_this: &mut State, cx| {\n cx.notify();\n }),\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n }) as Arc<dyn LanguageModel>\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n LanguageModelProviderId(PROVIDER_ID.into())\n }\n\n fn name(&self) -> LanguageModelProviderName {\n LanguageModelProviderName(PROVIDER_NAME.into())\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in AllLanguageModelSettings::get_global(cx)\n .deepseek\n .available_models\n .iter()\n {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state.update(cx, |state, cx| state.reset_api_key(cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| {\n let settings = &AllLanguageModelSettings::get_global(cx).deepseek;\n (state.api_key.clone(), settings.api_url.clone())\n }) else {\n return futures::future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let api_key = api_key.context(\"Missing DeepSeek API Key\")?;\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n LanguageModelProviderId(PROVIDER_ID.into())\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n LanguageModelProviderName(PROVIDER_NAME.into())\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::Other(anyhow!(error)))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::Other(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n if let Some(usage) = event.usage {\n events.push(Ok(LanguageModelCompletionEvent::UsageUpdate(TokenUsage {\n input_tokens: usage.prompt_tokens,\n output_tokens: usage.completion_tokens,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n })));\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Err(LanguageModelCompletionError::BadInputJson {\n id: tool_call.id.into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx);\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(api_key, cx))?\n .await\n })\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| state.update(cx, |state, cx| state.reset_api_key(cx))?.await)\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_from_env;\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {} environment variable.\",\n DEEPSEEK_API_KEY_VAR\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {}\", DEEPSEEK_API_KEY_VAR)\n } else {\n \"API key configured\".to_string()\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}, "5405c2c2d3d3939c8da20dd1b6f188681b5705c3": {"t": 1785757461.1461616, "n": 25237, "h": "06fc9e36bb54dd37", "c": "use anyhow::{Context as _, Result, anyhow};\nuse collections::{BTreeMap, HashMap};\nuse credentials_provider::CredentialsProvider;\nuse editor::{Editor, EditorElement, EditorStyle};\nuse futures::Stream;\nuse futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};\nuse gpui::{\n AnyView, AppContext as _, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle,\n WhiteSpace,\n};\nuse http_client::HttpClient;\nuse language_model::{\n AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,\n LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,\n LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,\n LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,\n RateLimiter, Role, StopReason,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse settings::{Settings, SettingsStore};\nuse std::pin::Pin;\nuse std::str::FromStr;\nuse std::sync::Arc;\nuse theme::ThemeSettings;\nuse ui::{Icon, IconName, List, prelude::*};\nuse util::ResultExt;\n\nuse crate::{AllLanguageModelSettings, ui::InstructionListItem};\n\nconst PROVIDER_ID: &str = \"deepseek\";\nconst PROVIDER_NAME: &str = \"DeepSeek\";\nconst DEEPSEEK_API_KEY_VAR: &str = \"DEEPSEEK_API_KEY\";\n\n#[derive(Default)]\nstruct RawToolCall {\n id: String,\n name: String,\n arguments: String,\n}\n\n#[derive(Default, Clone, Debug, PartialEq)]\npub struct DeepSeekSettings {\n pub api_url: String,\n pub available_models: Vec<AvailableModel>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct AvailableModel {\n pub name: String,\n pub display_name: Option<String>,\n pub max_tokens: u64,\n pub max_output_tokens: Option<u64>,\n}\n\npub struct DeepSeekLanguageModelProvider {\n http_client: Arc<dyn HttpClient>,\n state: Entity<State>,\n}\n\npub struct State {\n api_key: Option<String>,\n api_key_from_env: bool,\n _subscription: Subscription,\n}\n\nimpl State {\n fn is_authenticated(&self) -> bool {\n self.api_key.is_some()\n }\n\n fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .delete_credentials(&api_url, &cx)\n .await\n .log_err();\n this.update(cx, |this, cx| {\n this.api_key = None;\n this.api_key_from_env = false;\n cx.notify();\n })\n })\n }\n\n fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n credentials_provider\n .write_credentials(&api_url, \"Bearer\", api_key.as_bytes(), &cx)\n .await?;\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n cx.notify();\n })\n })\n }\n\n fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {\n if self.is_authenticated() {\n return Task::ready(Ok(()));\n }\n\n let credentials_provider = <dyn CredentialsProvider>::global(cx);\n let api_url = AllLanguageModelSettings::get_global(cx)\n .deepseek\n .api_url\n .clone();\n cx.spawn(async move |this, cx| {\n let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_API_KEY_VAR) {\n (api_key, true)\n } else {\n let (_, api_key) = credentials_provider\n .read_credentials(&api_url, &cx)\n .await?\n .ok_or(AuthenticateError::CredentialsNotFound)?;\n (\n String::from_utf8(api_key).context(\"invalid {PROVIDER_NAME} API key\")?,\n false,\n )\n };\n\n this.update(cx, |this, cx| {\n this.api_key = Some(api_key);\n this.api_key_from_env = from_env;\n cx.notify();\n })?;\n\n Ok(())\n })\n }\n}\n\nimpl DeepSeekLanguageModelProvider {\n pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {\n let state = cx.new(|cx| State {\n api_key: None,\n api_key_from_env: false,\n _subscription: cx.observe_global::<SettingsStore>(|_this: &mut State, cx| {\n cx.notify();\n }),\n });\n\n Self { http_client, state }\n }\n\n fn create_language_model(&self, model: deepseek::Model) -> Arc<dyn LanguageModel> {\n Arc::new(DeepSeekLanguageModel {\n id: LanguageModelId::from(model.id().to_string()),\n model,\n state: self.state.clone(),\n http_client: self.http_client.clone(),\n request_limiter: RateLimiter::new(4),\n }) as Arc<dyn LanguageModel>\n }\n}\n\nimpl LanguageModelProviderState for DeepSeekLanguageModelProvider {\n type ObservableEntity = State;\n\n fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {\n Some(self.state.clone())\n }\n}\n\nimpl LanguageModelProvider for DeepSeekLanguageModelProvider {\n fn id(&self) -> LanguageModelProviderId {\n LanguageModelProviderId(PROVIDER_ID.into())\n }\n\n fn name(&self) -> LanguageModelProviderName {\n LanguageModelProviderName(PROVIDER_NAME.into())\n }\n\n fn icon(&self) -> IconName {\n IconName::AiDeepSeek\n }\n\n fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default()))\n }\n\n fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {\n Some(self.create_language_model(deepseek::Model::default_fast()))\n }\n\n fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {\n let mut models = BTreeMap::default();\n\n models.insert(\"deepseek-chat\", deepseek::Model::Chat);\n models.insert(\"deepseek-reasoner\", deepseek::Model::Reasoner);\n\n for available_model in AllLanguageModelSettings::get_global(cx)\n .deepseek\n .available_models\n .iter()\n {\n models.insert(\n &available_model.name,\n deepseek::Model::Custom {\n name: available_model.name.clone(),\n display_name: available_model.display_name.clone(),\n max_tokens: available_model.max_tokens,\n max_output_tokens: available_model.max_output_tokens,\n },\n );\n }\n\n models\n .into_values()\n .map(|model| self.create_language_model(model))\n .collect()\n }\n\n fn is_authenticated(&self, cx: &App) -> bool {\n self.state.read(cx).is_authenticated()\n }\n\n fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {\n self.state.update(cx, |state, cx| state.authenticate(cx))\n }\n\n fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {\n cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))\n .into()\n }\n\n fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {\n self.state.update(cx, |state, cx| state.reset_api_key(cx))\n }\n}\n\npub struct DeepSeekLanguageModel {\n id: LanguageModelId,\n model: deepseek::Model,\n state: Entity<State>,\n http_client: Arc<dyn HttpClient>,\n request_limiter: RateLimiter,\n}\n\nimpl DeepSeekLanguageModel {\n fn stream_completion(\n &self,\n request: deepseek::Request,\n cx: &AsyncApp,\n ) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {\n let http_client = self.http_client.clone();\n let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| {\n let settings = &AllLanguageModelSettings::get_global(cx).deepseek;\n (state.api_key.clone(), settings.api_url.clone())\n }) else {\n return futures::future::ready(Err(anyhow!(\"App state dropped\"))).boxed();\n };\n\n let future = self.request_limiter.stream(async move {\n let api_key = api_key.context(\"Missing DeepSeek API Key\")?;\n let request =\n deepseek::stream_completion(http_client.as_ref(), &api_url, &api_key, request);\n let response = request.await?;\n Ok(response)\n });\n\n async move { Ok(future.await?.boxed()) }.boxed()\n }\n}\n\nimpl LanguageModel for DeepSeekLanguageModel {\n fn id(&self) -> LanguageModelId {\n self.id.clone()\n }\n\n fn name(&self) -> LanguageModelName {\n LanguageModelName::from(self.model.display_name().to_string())\n }\n\n fn provider_id(&self) -> LanguageModelProviderId {\n LanguageModelProviderId(PROVIDER_ID.into())\n }\n\n fn provider_name(&self) -> LanguageModelProviderName {\n LanguageModelProviderName(PROVIDER_NAME.into())\n }\n\n fn supports_tools(&self) -> bool {\n true\n }\n\n fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {\n true\n }\n\n fn supports_images(&self) -> bool {\n false\n }\n\n fn telemetry_id(&self) -> String {\n format!(\"deepseek/{}\", self.model.id())\n }\n\n fn max_token_count(&self) -> u64 {\n self.model.max_token_count()\n }\n\n fn max_output_tokens(&self) -> Option<u64> {\n self.model.max_output_tokens()\n }\n\n fn count_tokens(\n &self,\n request: LanguageModelRequest,\n cx: &App,\n ) -> BoxFuture<'static, Result<u64>> {\n cx.background_spawn(async move {\n let messages = request\n .messages\n .into_iter()\n .map(|message| tiktoken_rs::ChatCompletionRequestMessage {\n role: match message.role {\n Role::User => \"user\".into(),\n Role::Assistant => \"assistant\".into(),\n Role::System => \"system\".into(),\n },\n content: Some(message.string_contents()),\n name: None,\n function_call: None,\n })\n .collect::<Vec<_>>();\n\n tiktoken_rs::num_tokens_from_messages(\"gpt-4\", &messages).map(|tokens| tokens as u64)\n })\n .boxed()\n }\n\n fn stream_completion(\n &self,\n request: LanguageModelRequest,\n cx: &AsyncApp,\n ) -> BoxFuture<\n 'static,\n Result<\n BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,\n LanguageModelCompletionError,\n >,\n > {\n let request = into_deepseek(request, &self.model, self.max_output_tokens());\n let stream = self.stream_completion(request, cx);\n\n async move {\n let mapper = DeepSeekEventMapper::new();\n Ok(mapper.map_stream(stream.await?).boxed())\n }\n .boxed()\n }\n}\n\npub fn into_deepseek(\n request: LanguageModelRequest,\n model: &deepseek::Model,\n max_output_tokens: Option<u64>,\n) -> deepseek::Request {\n let is_reasoner = *model == deepseek::Model::Reasoner;\n\n let mut messages = Vec::new();\n for message in request.messages {\n for content in message.content {\n match content {\n MessageContent::Text(text) => messages.push(match message.role {\n Role::User => deepseek::RequestMessage::User { content: text },\n Role::Assistant => deepseek::RequestMessage::Assistant {\n content: Some(text),\n tool_calls: Vec::new(),\n },\n Role::System => deepseek::RequestMessage::System { content: text },\n }),\n MessageContent::Thinking { .. } => {}\n MessageContent::RedactedThinking(_) => {}\n MessageContent::Image(_) => {}\n MessageContent::ToolUse(tool_use) => {\n let tool_call = deepseek::ToolCall {\n id: tool_use.id.to_string(),\n content: deepseek::ToolCallContent::Function {\n function: deepseek::FunctionContent {\n name: tool_use.name.to_string(),\n arguments: serde_json::to_string(&tool_use.input)\n .unwrap_or_default(),\n },\n },\n };\n\n if let Some(deepseek::RequestMessage::Assistant { tool_calls, .. }) =\n messages.last_mut()\n {\n tool_calls.push(tool_call);\n } else {\n messages.push(deepseek::RequestMessage::Assistant {\n content: None,\n tool_calls: vec![tool_call],\n });\n }\n }\n MessageContent::ToolResult(tool_result) => {\n match &tool_result.content {\n LanguageModelToolResultContent::Text(text) => {\n messages.push(deepseek::RequestMessage::Tool {\n content: text.to_string(),\n tool_call_id: tool_result.tool_use_id.to_string(),\n });\n }\n LanguageModelToolResultContent::Image(_) => {}\n };\n }\n }\n }\n }\n\n deepseek::Request {\n model: model.id().to_string(),\n messages,\n stream: true,\n max_tokens: max_output_tokens,\n temperature: if is_reasoner {\n None\n } else {\n request.temperature\n },\n response_format: None,\n tools: request\n .tools\n .into_iter()\n .map(|tool| deepseek::ToolDefinition::Function {\n function: deepseek::FunctionDefinition {\n name: tool.name,\n description: Some(tool.description),\n parameters: Some(tool.input_schema),\n },\n })\n .collect(),\n }\n}\n\npub struct DeepSeekEventMapper {\n tool_calls_by_index: HashMap<usize, RawToolCall>,\n}\n\nimpl DeepSeekEventMapper {\n pub fn new() -> Self {\n Self {\n tool_calls_by_index: HashMap::default(),\n }\n }\n\n pub fn map_stream(\n mut self,\n events: Pin<Box<dyn Send + Stream<Item = Result<deepseek::StreamResponse>>>>,\n ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>\n {\n events.flat_map(move |event| {\n futures::stream::iter(match event {\n Ok(event) => self.map_event(event),\n Err(error) => vec![Err(LanguageModelCompletionError::Other(anyhow!(error)))],\n })\n })\n }\n\n pub fn map_event(\n &mut self,\n event: deepseek::StreamResponse,\n ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {\n let Some(choice) = event.choices.first() else {\n return vec![Err(LanguageModelCompletionError::Other(anyhow!(\n \"Response contained no choices\"\n )))];\n };\n\n let mut events = Vec::new();\n if let Some(content) = choice.delta.content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Text(content)));\n }\n\n if let Some(reasoning_content) = choice.delta.reasoning_content.clone() {\n events.push(Ok(LanguageModelCompletionEvent::Thinking {\n text: reasoning_content,\n signature: None,\n }));\n }\n\n if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {\n for tool_call in tool_calls {\n let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();\n\n if let Some(tool_id) = tool_call.id.clone() {\n entry.id = tool_id;\n }\n\n if let Some(function) = tool_call.function.as_ref() {\n if let Some(name) = function.name.clone() {\n entry.name = name;\n }\n\n if let Some(arguments) = function.arguments.clone() {\n entry.arguments.push_str(&arguments);\n }\n }\n }\n }\n\n match choice.finish_reason.as_deref() {\n Some(\"stop\") => {\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n Some(\"tool_calls\") => {\n events.extend(self.tool_calls_by_index.drain().map(|(_, tool_call)| {\n match serde_json::Value::from_str(&tool_call.arguments) {\n Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(\n LanguageModelToolUse {\n id: tool_call.id.clone().into(),\n name: tool_call.name.as_str().into(),\n is_input_complete: true,\n input,\n raw_input: tool_call.arguments.clone(),\n },\n )),\n Err(error) => Err(LanguageModelCompletionError::BadInputJson {\n id: tool_call.id.into(),\n tool_name: tool_call.name.as_str().into(),\n raw_input: tool_call.arguments.into(),\n json_parse_error: error.to_string(),\n }),\n }\n }));\n\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse)));\n }\n Some(stop_reason) => {\n log::error!(\"Unexpected DeepSeek stop_reason: {stop_reason:?}\",);\n events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn)));\n }\n None => {}\n }\n\n events\n }\n}\n\nstruct ConfigurationView {\n api_key_editor: Entity<Editor>,\n state: Entity<State>,\n load_credentials_task: Option<Task<()>>,\n}\n\nimpl ConfigurationView {\n fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n let api_key_editor = cx.new(|cx| {\n let mut editor = Editor::single_line(window, cx);\n editor.set_placeholder_text(\"sk-00000000000000000000000000000000\", cx);\n editor\n });\n\n cx.observe(&state, |_, _, cx| {\n cx.notify();\n })\n .detach();\n\n let load_credentials_task = Some(cx.spawn({\n let state = state.clone();\n async move |this, cx| {\n if let Some(task) = state\n .update(cx, |state, cx| state.authenticate(cx))\n .log_err()\n {\n let _ = task.await;\n }\n\n this.update(cx, |this, cx| {\n this.load_credentials_task = None;\n cx.notify();\n })\n .log_err();\n }\n }));\n\n Self {\n api_key_editor,\n state,\n load_credentials_task,\n }\n }\n\n fn save_api_key(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {\n let api_key = self.api_key_editor.read(cx).text(cx);\n if api_key.is_empty() {\n return;\n }\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| {\n state\n .update(cx, |state, cx| state.set_api_key(api_key, cx))?\n .await\n })\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n self.api_key_editor\n .update(cx, |editor, cx| editor.set_text(\"\", window, cx));\n\n let state = self.state.clone();\n cx.spawn(async move |_, cx| state.update(cx, |state, cx| state.reset_api_key(cx))?.await)\n .detach_and_log_err(cx);\n\n cx.notify();\n }\n\n fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {\n let settings = ThemeSettings::get_global(cx);\n let text_style = TextStyle {\n color: cx.theme().colors().text,\n font_family: settings.ui_font.family.clone(),\n font_features: settings.ui_font.features.clone(),\n font_fallbacks: settings.ui_font.fallbacks.clone(),\n font_size: rems(0.875).into(),\n font_weight: settings.ui_font.weight,\n font_style: FontStyle::Normal,\n line_height: relative(1.3),\n background_color: None,\n underline: None,\n strikethrough: None,\n white_space: WhiteSpace::Normal,\n ..Default::default()\n };\n EditorElement::new(\n &self.api_key_editor,\n EditorStyle {\n background: cx.theme().colors().editor_background,\n local_player: cx.theme().players().local(),\n text: text_style,\n ..Default::default()\n },\n )\n }\n\n fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {\n !self.state.read(cx).is_authenticated()\n }\n}\n\nimpl Render for ConfigurationView {\n fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n let env_var_set = self.state.read(cx).api_key_from_env;\n\n if self.load_credentials_task.is_some() {\n div().child(Label::new(\"Loading credentials...\")).into_any()\n } else if self.should_render_editor(cx) {\n v_flex()\n .size_full()\n .on_action(cx.listener(Self::save_api_key))\n .child(Label::new(\"To use DeepSeek in Zed, you need an API key:\"))\n .child(\n List::new()\n .child(InstructionListItem::new(\n \"Get your API key from the\",\n Some(\"DeepSeek console\"),\n Some(\"https://platform.deepseek.com/api_keys\"),\n ))\n .child(InstructionListItem::text_only(\n \"Paste your API key below and hit enter to start using the assistant\",\n )),\n )\n .child(\n h_flex()\n .w_full()\n .my_2()\n .px_2()\n .py_1()\n .bg(cx.theme().colors().editor_background)\n .border_1()\n .border_color(cx.theme().colors().border)\n .rounded_sm()\n .child(self.render_api_key_editor(cx)),\n )\n .child(\n Label::new(format!(\n \"Or set the {} environment variable.\",\n DEEPSEEK_API_KEY_VAR\n ))\n .size(LabelSize::Small)\n .color(Color::Muted),\n )\n .into_any()\n } else {\n h_flex()\n .mt_1()\n .p_1()\n .justify_between()\n .rounded_md()\n .border_1()\n .border_color(cx.theme().colors().border)\n .bg(cx.theme().colors().background)\n .child(\n h_flex()\n .gap_1()\n .child(Icon::new(IconName::Check).color(Color::Success))\n .child(Label::new(if env_var_set {\n format!(\"API key set in {}\", DEEPSEEK_API_KEY_VAR)\n } else {\n \"API key configured\".to_string()\n })),\n )\n .child(\n Button::new(\"reset-key\", \"Reset Key\")\n .label_size(LabelSize::Small)\n .icon(Some(IconName::Trash))\n .icon_size(IconSize::Small)\n .icon_position(IconPosition::Start)\n .disabled(env_var_set)\n .on_click(\n cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)),\n ),\n )\n .into_any()\n }\n }\n}\n"}}}}