pub mod responses; use std::path::PathBuf;use std::sync::Arc;use std::sync::OnceLock; use anyhow::Context as _;use anyhow::{Result, anyhow};use collections::HashSet;use fs::Fs;use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};use gpui::WeakEntity;use gpui::{App, AsyncApp, Global, prelude::*};use http_client::HttpRequestExt;use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};use paths::home_dir;use serde::{Deserialize, Serialize}; use settings::watch_config_dir; pub const COPILOT_OAUTH_ENV_VAR: &str = "GH_COPILOT_TOKEN";const DEFAULT_COPILOT_API_ENDPOINT: &str = "https://api.githubcopilot.com"; #[derive(Default, Clone, Debug, PartialEq)]pub struct CopilotChatConfiguration { pub enterprise_uri: Option<String>,} impl CopilotChatConfiguration { pub fn oauth_domain(&self) -> String { if let Some(enterprise_uri) = &self.enterprise_uri { Self::parse_domain(enterprise_uri) } else { "github.com".to_string() } } pub fn graphql_url(&self) -> String { if let Some(enterprise_uri) = &self.enterprise_uri { let domain = Self::parse_domain(enterprise_uri); format!("https://{}/api/graphql", domain) } else { "https://api.github.com/graphql".to_string() } } pub fn chat_completions_url(&self, api_endpoint: &str) -> String { format!("{}/chat/completions", api_endpoint) } pub fn responses_url(&self, api_endpoint: &str) -> String { format!("{}/responses", api_endpoint) } pub fn messages_url(&self, api_endpoint: &str) -> String { format!("{}/v1/messages", api_endpoint) } pub fn models_url(&self, api_endpoint: &str) -> String { format!("{}/models", api_endpoint) } fn parse_domain(enterprise_uri: &str) -> String { let uri = enterprise_uri.trim_end_matches('/'); if let Some(domain) = uri.strip_prefix("https://") { domain.split('/').next().unwrap_or(domain).to_string() } else if let Some(domain) = uri.strip_prefix("http://") { domain.split('/').next().unwrap_or(domain).to_string() } else { uri.split('/').next().unwrap_or(uri).to_string() } }} #[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]#[serde(rename_all = "lowercase")]pub enum Role { User, Assistant, System,} #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]pub enum ChatLocation { #[default] Panel, Editor, EditingSession, Terminal, Agent, Other,} impl ChatLocation { pub fn to_intent_string(self) -> &'static str { match self { ChatLocation::Panel => "conversation-panel", ChatLocation::Editor => "conversation-inline", ChatLocation::EditingSession => "conversation-edits", ChatLocation::Terminal => "conversation-terminal", ChatLocation::Agent => "conversation-agent", ChatLocation::Other => "conversation-other", } }} #[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]pub enum ModelSupportedEndpoint { #[serde(rename = "/chat/completions")] ChatCompletions, #[serde(rename = "/responses")] Responses, #[serde(rename = "/v1/messages")] Messages, /// Unknown endpoint that we don't explicitly support yet #[serde(other)] Unknown,} #[derive(Deserialize)]struct ModelSchema { #[serde(deserialize_with = "deserialize_models_skip_errors")] data: Vec<Model>,} fn deserialize_models_skip_errors<'de, D>(deserializer: D) -> Result<Vec<Model>, D::Error>where D: serde::Deserializer<'de>,{ let raw_values = Vec::<serde_json::Value>::deserialize(deserializer)?; let models = raw_values .into_iter() .filter_map(|value| match serde_json::from_value::<Model>(value) { Ok(model) => Some(model), Err(err) => { log::warn!("GitHub Copilot Chat model failed to deserialize: {:?}", err); None } }) .collect(); Ok(models)} #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]pub struct Model { billing: ModelBilling, capabilities: ModelCapabilities, id: String, name: String, policy: Option<ModelPolicy>, vendor: ModelVendor, is_chat_default: bool, // The model with this value true is selected by VSCode copilot if a premium request limit is // reached. Zed does not currently implement this behaviour is_chat_fallback: bool, model_picker_enabled: bool, #[serde(default)] supported_endpoints: Vec<ModelSupportedEndpoint>,} #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]struct ModelBilling { is_premium: bool, multiplier: f64, // List of plans a model is restricted to // Field is not present if a model is available for all plans #[serde(default)] restricted_to: Option<Vec<String>>,} #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]struct ModelCapabilities { family: String, #[serde(default)] limits: ModelLimits, supports: ModelSupportedFeatures, #[serde(rename = "type")] model_type: String, #[serde(default)] tokenizer: Option<String>,} #[derive(Default, Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]struct ModelLimits { #[serde(default)] max_context_window_tokens: usize, #[serde(default)] max_output_tokens: usize, #[serde(default)] max_prompt_tokens: u64,} #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]struct ModelPolicy { state: String,} #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]struct ModelSupportedFeatures { #[serde(default)] streaming: bool, #[serde(default)] tool_calls: bool, #[serde(default)] parallel_tool_calls: bool, #[serde(default)] vision: bool, #[serde(default)] thinking: bool, #[serde(default)] adaptive_thinking: bool, #[serde(default)] max_thinking_budget: Option<u32>, #[serde(default)] min_thinking_budget: Option<u32>, #[serde(default)] reasoning_effort: Vec<String>,} #[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]pub enum ModelVendor { // Azure OpenAI should have no functional difference from OpenAI in Copilot Chat #[serde(alias = "Azure OpenAI")] OpenAI, Google, Anthropic, #[serde(rename = "xAI")] XAI, /// Unknown vendor that we don't explicitly support yet #[serde(other)] Unknown,} #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]#[serde(tag = "type")]pub enum ChatMessagePart { #[serde(rename = "text")] Text { text: String }, #[serde(rename = "image_url")] Image { image_url: ImageUrl },} #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]pub struct ImageUrl { pub url: String,} impl Model { pub fn uses_streaming(&self) -> bool { self.capabilities.supports.streaming } pub fn id(&self) -> &str { self.id.as_str() } pub fn display_name(&self) -> &str { self.name.as_str() } pub fn max_token_count(&self) -> u64 { self.capabilities.limits.max_context_window_tokens as u64 } pub fn max_output_tokens(&self) -> usize { self.capabilities.limits.max_output_tokens } pub fn supports_tools(&self) -> bool { self.capabilities.supports.tool_calls } pub fn vendor(&self) -> ModelVendor { self.vendor } pub fn supports_vision(&self) -> bool { self.capabilities.supports.vision } pub fn supports_parallel_tool_calls(&self) -> bool { self.capabilities.supports.parallel_tool_calls } pub fn tokenizer(&self) -> Option<&str> { self.capabilities.tokenizer.as_deref() } pub fn supports_response(&self) -> bool { self.supported_endpoints.len() > 0 && !self .supported_endpoints .contains(&ModelSupportedEndpoint::ChatCompletions) && self .supported_endpoints .contains(&ModelSupportedEndpoint::Responses) } pub fn supports_messages(&self) -> bool { self.supported_endpoints .contains(&ModelSupportedEndpoint::Messages) } pub fn supports_thinking(&self) -> bool { self.capabilities.supports.thinking } pub fn supports_adaptive_thinking(&self) -> bool { self.capabilities.supports.adaptive_thinking } pub fn can_think(&self) -> bool { self.supports_thinking() || self.supports_adaptive_thinking() || self.max_thinking_budget().is_some() } pub fn max_thinking_budget(&self) -> Option<u32> { self.capabilities.supports.max_thinking_budget } pub fn min_thinking_budget(&self) -> Option<u32> { self.capabilities.supports.min_thinking_budget } pub fn reasoning_effort_levels(&self) -> &[String] { &self.capabilities.supports.reasoning_effort } pub fn family(&self) -> &str { &self.capabilities.family } pub fn multiplier(&self) -> f64 { self.billing.multiplier }} #[derive(Serialize, Deserialize)]pub struct Request { pub n: usize, pub stream: bool, pub temperature: f32, pub model: String, pub messages: Vec<ChatMessage>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub tools: Vec<Tool>, #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_choice: Option<ToolChoice>, #[serde(skip_serializing_if = "Option::is_none")] pub thinking_budget: Option<u32>,} #[derive(Serialize, Deserialize)]pub struct Function { pub name: String, pub description: String, pub parameters: serde_json::Value,} #[derive(Serialize, Deserialize)]#[serde(tag = "type", rename_all = "snake_case")]pub enum Tool { Function { function: Function },} #[derive(Serialize, Deserialize, Debug)]#[serde(rename_all = "lowercase")]pub enum ToolChoice { Auto, Any, None,} #[derive(Serialize, Deserialize, Debug)]#[serde(tag = "role", rename_all = "lowercase")]pub enum ChatMessage { Assistant { content: ChatMessageContent, #[serde(default, skip_serializing_if = "Vec::is_empty")] tool_calls: Vec<ToolCall>, #[serde(default, skip_serializing_if = "Option::is_none")] reasoning_opaque: Option<String>, #[serde(default, skip_serializing_if = "Option::is_none")] reasoning_text: Option<String>, }, User { content: ChatMessageContent, }, System { content: String, }, Tool { content: ChatMessageContent, tool_call_id: String, },} #[derive(Debug, Serialize, Deserialize)]#[serde(untagged)]pub enum ChatMessageContent { Plain(String), Multipart(Vec<ChatMessagePart>),} impl ChatMessageContent { pub fn empty() -> Self { ChatMessageContent::Multipart(vec![]) }} impl From<Vec<ChatMessagePart>> for ChatMessageContent { fn from(mut parts: Vec<ChatMessagePart>) -> Self { if let [ChatMessagePart::Text { text }] = parts.as_mut_slice() { ChatMessageContent::Plain(std::mem::take(text)) } else { ChatMessageContent::Multipart(parts) } }} impl From<String> for ChatMessageContent { fn from(text: String) -> Self { ChatMessageContent::Plain(text) }} #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]pub struct ToolCall { pub id: String, #[serde(flatten)] pub content: ToolCallContent,} #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]#[serde(tag = "type", rename_all = "lowercase")]pub enum ToolCallContent { Function { function: FunctionContent },} #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]pub struct FunctionContent { pub name: String, pub arguments: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub thought_signature: Option<String>,} #[derive(Deserialize, Debug)]#[serde(tag = "type", rename_all = "snake_case")]pub struct ResponseEvent { pub choices: Vec<ResponseChoice>, pub id: String, pub usage: Option<Usage>,} #[derive(Deserialize, Debug)]pub struct Usage { pub completion_tokens: u64, pub prompt_tokens: u64, pub total_tokens: u64,} #[derive(Debug, Deserialize)]pub struct ResponseChoice { pub index: Option<usize>, pub finish_reason: Option<String>, pub delta: Option<ResponseDelta>, pub message: Option<ResponseDelta>,} #[derive(Debug, Deserialize)]pub struct ResponseDelta { pub content: Option<String>, pub role: Option<Role>, #[serde(default)] pub tool_calls: Vec<ToolCallChunk>, pub reasoning_opaque: Option<String>, pub reasoning_text: Option<String>,}#[derive(Deserialize, Debug, Eq, PartialEq)]pub struct ToolCallChunk { pub index: Option<usize>, pub id: Option<String>, pub function: Option<FunctionChunk>,} #[derive(Deserialize, Debug, Eq, PartialEq)]pub struct FunctionChunk { pub name: Option<String>, pub arguments: Option<String>, pub thought_signature: Option<String>,} struct GlobalCopilotChat(gpui::Entity<CopilotChat>); impl Global for GlobalCopilotChat {} Lines 1–500 of 1739. Load more
pub mod responses; use std::path::PathBuf; use std::sync::Arc; use std::sync::OnceLock; use anyhow::Context as _; use anyhow::{Result, anyhow}; use collections::HashSet; use fs::Fs; use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream}; use gpui::WeakEntity; use gpui::{App, AsyncApp, Global, prelude::*}; use http_client::HttpRequestExt; use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; use paths::home_dir; use serde::{Deserialize, Serialize}; use settings::watch_config_dir; pub const COPILOT_OAUTH_ENV_VAR: &str = "GH_COPILOT_TOKEN"; const DEFAULT_COPILOT_API_ENDPOINT: &str = "https://api.githubcopilot.com"; #[derive(Default, Clone, Debug, PartialEq)] pub struct CopilotChatConfiguration { pub enterprise_uri: Option<String>, } impl CopilotChatConfiguration { pub fn oauth_domain(&self) -> String { if let Some(enterprise_uri) = &self.enterprise_uri { Self::parse_domain(enterprise_uri) } else { "github.com".to_string() } } pub fn graphql_url(&self) -> String { if let Some(enterprise_uri) = &self.enterprise_uri { let domain = Self::parse_domain(enterprise_uri); format!("https://{}/api/graphql", domain) } else { "https://api.github.com/graphql".to_string() } } pub fn chat_completions_url(&self, api_endpoint: &str) -> String { format!("{}/chat/completions", api_endpoint) } pub fn responses_url(&self, api_endpoint: &str) -> String { format!("{}/responses", api_endpoint) } pub fn messages_url(&self, api_endpoint: &str) -> String { format!("{}/v1/messages", api_endpoint) } pub fn models_url(&self, api_endpoint: &str) -> String { format!("{}/models", api_endpoint) } fn parse_domain(enterprise_uri: &str) -> String { let uri = enterprise_uri.trim_end_matches('/'); if let Some(domain) = uri.strip_prefix("https://") { domain.split('/').next().unwrap_or(domain).to_string() } else if let Some(domain) = uri.strip_prefix("http://") { domain.split('/').next().unwrap_or(domain).to_string() } else { uri.split('/').next().unwrap_or(uri).to_string() } } } #[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)] #[serde(rename_all = "lowercase")] pub enum Role { User, Assistant, System, } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum ChatLocation { #[default] Panel, Editor, EditingSession, Terminal, Agent, Other, } impl ChatLocation { pub fn to_intent_string(self) -> &'static str { match self { ChatLocation::Panel => "conversation-panel", ChatLocation::Editor => "conversation-inline", ChatLocation::EditingSession => "conversation-edits", ChatLocation::Terminal => "conversation-terminal", ChatLocation::Agent => "conversation-agent", ChatLocation::Other => "conversation-other", } } } #[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] pub enum ModelSupportedEndpoint { #[serde(rename = "/chat/completions")] ChatCompletions, #[serde(rename = "/responses")] Responses, #[serde(rename = "/v1/messages")] Messages, /// Unknown endpoint that we don't explicitly support yet #[serde(other)] Unknown, } #[derive(Deserialize)] struct ModelSchema { #[serde(deserialize_with = "deserialize_models_skip_errors")] data: Vec<Model>, } fn deserialize_models_skip_errors<'de, D>(deserializer: D) -> Result<Vec<Model>, D::Error> where D: serde::Deserializer<'de>, { let raw_values = Vec::<serde_json::Value>::deserialize(deserializer)?; let models = raw_values .into_iter() .filter_map(|value| match serde_json::from_value::<Model>(value) { Ok(model) => Some(model), Err(err) => { log::warn!("GitHub Copilot Chat model failed to deserialize: {:?}", err); None } }) .collect(); Ok(models) } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Model { billing: ModelBilling, capabilities: ModelCapabilities, id: String, name: String, policy: Option<ModelPolicy>, vendor: ModelVendor, is_chat_default: bool, // The model with this value true is selected by VSCode copilot if a premium request limit is // reached. Zed does not currently implement this behaviour is_chat_fallback: bool, model_picker_enabled: bool, #[serde(default)] supported_endpoints: Vec<ModelSupportedEndpoint>, } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] struct ModelBilling { is_premium: bool, multiplier: f64, // List of plans a model is restricted to // Field is not present if a model is available for all plans #[serde(default)] restricted_to: Option<Vec<String>>, } #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] struct ModelCapabilities { family: String, #[serde(default)] limits: ModelLimits, supports: ModelSupportedFeatures, #[serde(rename = "type")] model_type: String, #[serde(default)] tokenizer: Option<String>, } #[derive(Default, Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] struct ModelLimits { #[serde(default)] max_context_window_tokens: usize, #[serde(default)] max_output_tokens: usize, #[serde(default)] max_prompt_tokens: u64, } #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] struct ModelPolicy { state: String, } #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] struct ModelSupportedFeatures { #[serde(default)] streaming: bool, #[serde(default)] tool_calls: bool, #[serde(default)] parallel_tool_calls: bool, #[serde(default)] vision: bool, #[serde(default)] thinking: bool, #[serde(default)] adaptive_thinking: bool, #[serde(default)] max_thinking_budget: Option<u32>, #[serde(default)] min_thinking_budget: Option<u32>, #[serde(default)] reasoning_effort: Vec<String>, } #[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)] pub enum ModelVendor { // Azure OpenAI should have no functional difference from OpenAI in Copilot Chat #[serde(alias = "Azure OpenAI")] OpenAI, Google, Anthropic, #[serde(rename = "xAI")] XAI, /// Unknown vendor that we don't explicitly support yet #[serde(other)] Unknown, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] #[serde(tag = "type")] pub enum ChatMessagePart { #[serde(rename = "text")] Text { text: String }, #[serde(rename = "image_url")] Image { image_url: ImageUrl }, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] pub struct ImageUrl { pub url: String, } impl Model { pub fn uses_streaming(&self) -> bool { self.capabilities.supports.streaming } pub fn id(&self) -> &str { self.id.as_str() } pub fn display_name(&self) -> &str { self.name.as_str() } pub fn max_token_count(&self) -> u64 { self.capabilities.limits.max_context_window_tokens as u64 } pub fn max_output_tokens(&self) -> usize { self.capabilities.limits.max_output_tokens } pub fn supports_tools(&self) -> bool { self.capabilities.supports.tool_calls } pub fn vendor(&self) -> ModelVendor { self.vendor } pub fn supports_vision(&self) -> bool { self.capabilities.supports.vision } pub fn supports_parallel_tool_calls(&self) -> bool { self.capabilities.supports.parallel_tool_calls } pub fn tokenizer(&self) -> Option<&str> { self.capabilities.tokenizer.as_deref() } pub fn supports_response(&self) -> bool { self.supported_endpoints.len() > 0 && !self .supported_endpoints .contains(&ModelSupportedEndpoint::ChatCompletions) && self .supported_endpoints .contains(&ModelSupportedEndpoint::Responses) } pub fn supports_messages(&self) -> bool { self.supported_endpoints .contains(&ModelSupportedEndpoint::Messages) } pub fn supports_thinking(&self) -> bool { self.capabilities.supports.thinking } pub fn supports_adaptive_thinking(&self) -> bool { self.capabilities.supports.adaptive_thinking } pub fn can_think(&self) -> bool { self.supports_thinking() || self.supports_adaptive_thinking() || self.max_thinking_budget().is_some() } pub fn max_thinking_budget(&self) -> Option<u32> { self.capabilities.supports.max_thinking_budget } pub fn min_thinking_budget(&self) -> Option<u32> { self.capabilities.supports.min_thinking_budget } pub fn reasoning_effort_levels(&self) -> &[String] { &self.capabilities.supports.reasoning_effort } pub fn family(&self) -> &str { &self.capabilities.family } pub fn multiplier(&self) -> f64 { self.billing.multiplier } } #[derive(Serialize, Deserialize)] pub struct Request { pub n: usize, pub stream: bool, pub temperature: f32, pub model: String, pub messages: Vec<ChatMessage>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub tools: Vec<Tool>, #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_choice: Option<ToolChoice>, #[serde(skip_serializing_if = "Option::is_none")] pub thinking_budget: Option<u32>, } #[derive(Serialize, Deserialize)] pub struct Function { pub name: String, pub description: String, pub parameters: serde_json::Value, } #[derive(Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum Tool { Function { function: Function }, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "lowercase")] pub enum ToolChoice { Auto, Any, None, } #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "role", rename_all = "lowercase")] pub enum ChatMessage { Assistant { content: ChatMessageContent, #[serde(default, skip_serializing_if = "Vec::is_empty")] tool_calls: Vec<ToolCall>, #[serde(default, skip_serializing_if = "Option::is_none")] reasoning_opaque: Option<String>, #[serde(default, skip_serializing_if = "Option::is_none")] reasoning_text: Option<String>, }, User { content: ChatMessageContent, }, System { content: String, }, Tool { content: ChatMessageContent, tool_call_id: String, }, } #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum ChatMessageContent { Plain(String), Multipart(Vec<ChatMessagePart>), } impl ChatMessageContent { pub fn empty() -> Self { ChatMessageContent::Multipart(vec![]) } } impl From<Vec<ChatMessagePart>> for ChatMessageContent { fn from(mut parts: Vec<ChatMessagePart>) -> Self { if let [ChatMessagePart::Text { text }] = parts.as_mut_slice() { ChatMessageContent::Plain(std::mem::take(text)) } else { ChatMessageContent::Multipart(parts) } } } impl From<String> for ChatMessageContent { fn from(text: String) -> Self { ChatMessageContent::Plain(text) } } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct ToolCall { pub id: String, #[serde(flatten)] pub content: ToolCallContent, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[serde(tag = "type", rename_all = "lowercase")] pub enum ToolCallContent { Function { function: FunctionContent }, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct FunctionContent { pub name: String, pub arguments: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub thought_signature: Option<String>, } #[derive(Deserialize, Debug)] #[serde(tag = "type", rename_all = "snake_case")] pub struct ResponseEvent { pub choices: Vec<ResponseChoice>, pub id: String, pub usage: Option<Usage>, } #[derive(Deserialize, Debug)] pub struct Usage { pub completion_tokens: u64, pub prompt_tokens: u64, pub total_tokens: u64, } #[derive(Debug, Deserialize)] pub struct ResponseChoice { pub index: Option<usize>, pub finish_reason: Option<String>, pub delta: Option<ResponseDelta>, pub message: Option<ResponseDelta>, } #[derive(Debug, Deserialize)] pub struct ResponseDelta { pub content: Option<String>, pub role: Option<Role>, #[serde(default)] pub tool_calls: Vec<ToolCallChunk>, pub reasoning_opaque: Option<String>, pub reasoning_text: Option<String>, } #[derive(Deserialize, Debug, Eq, PartialEq)] pub struct ToolCallChunk { pub index: Option<usize>, pub id: Option<String>, pub function: Option<FunctionChunk>, } #[derive(Deserialize, Debug, Eq, PartialEq)] pub struct FunctionChunk { pub name: Option<String>, pub arguments: Option<String>, pub thought_signature: Option<String>, } struct GlobalCopilotChat(gpui::Entity<CopilotChat>); impl Global for GlobalCopilotChat {}