copilot: Allow enterprise to sign in and use copilot (#32296)
This addresses: https://github.com/zed-industries/zed/pull/32248#issuecomment-2952060834. This PR address two main things one allowing enterprise users to use copilot chat and completion while also introducing the new way to handle copilot url specific their subscription. Simplifying the UX around the github copilot and removes the burden of users figuring out what url to use for their subscription. - [x] Pass enterprise_uri to copilot lsp so that it can redirect users to their enterprise server. Ref: https://github.com/github/copilot-language-server-release#configuration-management - [x] Remove the old ui and config language_models.copilot which allowed users to specify their copilot_chat specific endpoint. We now derive that automatically using token endpoint for copilot so that we can send the requests to specific copilot endpoint for depending upon the url returned by copilot server. - [x] Tested this for checking the both enterprise and non-enterprise flow work. Thanks to @theherk for the help to debug and test it. - [ ] Udpdate the zed.dev/docs to refelect how to setup enterprise copilot. What this doesn't do at the moment: * Currently zed doesn't allow to have two seperate accounts as the token used in chat is same as the one generated by lsp. After this changes also this behaviour remains same and users can't have both enterprise and personal copilot installed. P.S: Might need to do some bit of code cleanup and other things but overall I felt this PR was ready for atleast first pass of review to gather feedback around the implementation and code itself. Release Notes: - Add enterprise support for GitHub copilot --------- Signed-off-by: Umesh Yadav <git@umesh.dev>
This commit is contained in:
parent
c4355d2905
commit
b13144eb1f
8 changed files with 214 additions and 283 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -8981,6 +8981,7 @@ dependencies = [
|
||||||
"gpui",
|
"gpui",
|
||||||
"gpui_tokio",
|
"gpui_tokio",
|
||||||
"http_client",
|
"http_client",
|
||||||
|
"language",
|
||||||
"language_model",
|
"language_model",
|
||||||
"lmstudio",
|
"lmstudio",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -24,6 +24,7 @@ use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServer
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use request::StatusNotification;
|
use request::StatusNotification;
|
||||||
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use sign_in::{reinstall_and_sign_in_within_workspace, sign_out_within_workspace};
|
use sign_in::{reinstall_and_sign_in_within_workspace, sign_out_within_workspace};
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
|
@ -61,7 +62,15 @@ pub fn init(
|
||||||
node_runtime: NodeRuntime,
|
node_runtime: NodeRuntime,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
copilot_chat::init(fs.clone(), http.clone(), cx);
|
let language_settings = all_language_settings(None, cx);
|
||||||
|
let configuration = copilot_chat::CopilotChatConfiguration {
|
||||||
|
enterprise_uri: language_settings
|
||||||
|
.edit_predictions
|
||||||
|
.copilot
|
||||||
|
.enterprise_uri
|
||||||
|
.clone(),
|
||||||
|
};
|
||||||
|
copilot_chat::init(fs.clone(), http.clone(), configuration, cx);
|
||||||
|
|
||||||
let copilot = cx.new({
|
let copilot = cx.new({
|
||||||
let node_runtime = node_runtime.clone();
|
let node_runtime = node_runtime.clone();
|
||||||
|
@ -347,8 +356,11 @@ impl Copilot {
|
||||||
_subscription: cx.on_app_quit(Self::shutdown_language_server),
|
_subscription: cx.on_app_quit(Self::shutdown_language_server),
|
||||||
};
|
};
|
||||||
this.start_copilot(true, false, cx);
|
this.start_copilot(true, false, cx);
|
||||||
cx.observe_global::<SettingsStore>(move |this, cx| this.start_copilot(true, false, cx))
|
cx.observe_global::<SettingsStore>(move |this, cx| {
|
||||||
.detach();
|
this.start_copilot(true, false, cx);
|
||||||
|
this.send_configuration_update(cx);
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,6 +447,43 @@ impl Copilot {
|
||||||
if env.is_empty() { None } else { Some(env) }
|
if env.is_empty() { None } else { Some(env) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_configuration_update(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let copilot_settings = all_language_settings(None, cx)
|
||||||
|
.edit_predictions
|
||||||
|
.copilot
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let settings = json!({
|
||||||
|
"http": {
|
||||||
|
"proxy": copilot_settings.proxy,
|
||||||
|
"proxyStrictSSL": !copilot_settings.proxy_no_verify.unwrap_or(false)
|
||||||
|
},
|
||||||
|
"github-enterprise": {
|
||||||
|
"uri": copilot_settings.enterprise_uri
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(copilot_chat) = copilot_chat::CopilotChat::global(cx) {
|
||||||
|
copilot_chat.update(cx, |chat, cx| {
|
||||||
|
chat.set_configuration(
|
||||||
|
copilot_chat::CopilotChatConfiguration {
|
||||||
|
enterprise_uri: copilot_settings.enterprise_uri.clone(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(server) = self.server.as_running() {
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidChangeConfiguration>(
|
||||||
|
&lsp::DidChangeConfigurationParams { settings },
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) {
|
pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) {
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
|
@ -541,12 +590,6 @@ impl Copilot {
|
||||||
.into_response()
|
.into_response()
|
||||||
.context("copilot: check status")?;
|
.context("copilot: check status")?;
|
||||||
|
|
||||||
server
|
|
||||||
.request::<request::SetEditorInfo>(editor_info)
|
|
||||||
.await
|
|
||||||
.into_response()
|
|
||||||
.context("copilot: set editor info")?;
|
|
||||||
|
|
||||||
anyhow::Ok((server, status))
|
anyhow::Ok((server, status))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -564,6 +607,8 @@ impl Copilot {
|
||||||
});
|
});
|
||||||
cx.emit(Event::CopilotLanguageServerStarted);
|
cx.emit(Event::CopilotLanguageServerStarted);
|
||||||
this.update_sign_in_status(status, cx);
|
this.update_sign_in_status(status, cx);
|
||||||
|
// Send configuration now that the LSP is fully started
|
||||||
|
this.send_configuration_update(cx);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
this.server = CopilotServer::Error(error.to_string().into());
|
this.server = CopilotServer::Error(error.to_string().into());
|
||||||
|
|
|
@ -19,10 +19,47 @@ use settings::watch_config_dir;
|
||||||
pub const COPILOT_OAUTH_ENV_VAR: &str = "GH_COPILOT_TOKEN";
|
pub const COPILOT_OAUTH_ENV_VAR: &str = "GH_COPILOT_TOKEN";
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, PartialEq)]
|
#[derive(Default, Clone, Debug, PartialEq)]
|
||||||
pub struct CopilotChatSettings {
|
pub struct CopilotChatConfiguration {
|
||||||
pub api_url: Arc<str>,
|
pub enterprise_uri: Option<String>,
|
||||||
pub auth_url: Arc<str>,
|
}
|
||||||
pub models_url: Arc<str>,
|
|
||||||
|
impl CopilotChatConfiguration {
|
||||||
|
pub fn token_url(&self) -> String {
|
||||||
|
if let Some(enterprise_uri) = &self.enterprise_uri {
|
||||||
|
let domain = Self::parse_domain(enterprise_uri);
|
||||||
|
format!("https://api.{}/copilot_internal/v2/token", domain)
|
||||||
|
} else {
|
||||||
|
"https://api.github.com/copilot_internal/v2/token".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 api_url_from_endpoint(&self, endpoint: &str) -> String {
|
||||||
|
format!("{}/chat/completions", endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn models_url_from_endpoint(&self, endpoint: &str) -> String {
|
||||||
|
format!("{}/models", 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copilot's base model; defined by Microsoft in premium requests table
|
// Copilot's base model; defined by Microsoft in premium requests table
|
||||||
|
@ -309,12 +346,19 @@ pub struct FunctionChunk {
|
||||||
struct ApiTokenResponse {
|
struct ApiTokenResponse {
|
||||||
token: String,
|
token: String,
|
||||||
expires_at: i64,
|
expires_at: i64,
|
||||||
|
endpoints: ApiTokenResponseEndpoints,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ApiTokenResponseEndpoints {
|
||||||
|
api: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ApiToken {
|
struct ApiToken {
|
||||||
api_key: String,
|
api_key: String,
|
||||||
expires_at: DateTime<chrono::Utc>,
|
expires_at: DateTime<chrono::Utc>,
|
||||||
|
api_endpoint: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApiToken {
|
impl ApiToken {
|
||||||
|
@ -335,6 +379,7 @@ impl TryFrom<ApiTokenResponse> for ApiToken {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
api_key: response.token,
|
api_key: response.token,
|
||||||
expires_at,
|
expires_at,
|
||||||
|
api_endpoint: response.endpoints.api,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,13 +391,18 @@ impl Global for GlobalCopilotChat {}
|
||||||
pub struct CopilotChat {
|
pub struct CopilotChat {
|
||||||
oauth_token: Option<String>,
|
oauth_token: Option<String>,
|
||||||
api_token: Option<ApiToken>,
|
api_token: Option<ApiToken>,
|
||||||
settings: CopilotChatSettings,
|
configuration: CopilotChatConfiguration,
|
||||||
models: Option<Vec<Model>>,
|
models: Option<Vec<Model>>,
|
||||||
client: Arc<dyn HttpClient>,
|
client: Arc<dyn HttpClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut App) {
|
pub fn init(
|
||||||
let copilot_chat = cx.new(|cx| CopilotChat::new(fs, client, cx));
|
fs: Arc<dyn Fs>,
|
||||||
|
client: Arc<dyn HttpClient>,
|
||||||
|
configuration: CopilotChatConfiguration,
|
||||||
|
cx: &mut App,
|
||||||
|
) {
|
||||||
|
let copilot_chat = cx.new(|cx| CopilotChat::new(fs, client, configuration, cx));
|
||||||
cx.set_global(GlobalCopilotChat(copilot_chat));
|
cx.set_global(GlobalCopilotChat(copilot_chat));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,10 +430,15 @@ impl CopilotChat {
|
||||||
.map(|model| model.0.clone())
|
.map(|model| model.0.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut Context<Self>) -> Self {
|
fn new(
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
client: Arc<dyn HttpClient>,
|
||||||
|
configuration: CopilotChatConfiguration,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
let config_paths: HashSet<PathBuf> = copilot_chat_config_paths().into_iter().collect();
|
let config_paths: HashSet<PathBuf> = copilot_chat_config_paths().into_iter().collect();
|
||||||
let dir_path = copilot_chat_config_dir();
|
let dir_path = copilot_chat_config_dir();
|
||||||
let settings = CopilotChatSettings::default();
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let mut parent_watch_rx = watch_config_dir(
|
let mut parent_watch_rx = watch_config_dir(
|
||||||
cx.background_executor(),
|
cx.background_executor(),
|
||||||
|
@ -392,7 +447,9 @@ impl CopilotChat {
|
||||||
config_paths,
|
config_paths,
|
||||||
);
|
);
|
||||||
while let Some(contents) = parent_watch_rx.next().await {
|
while let Some(contents) = parent_watch_rx.next().await {
|
||||||
let oauth_token = extract_oauth_token(contents);
|
let oauth_domain =
|
||||||
|
this.read_with(cx, |this, _| this.configuration.oauth_domain())?;
|
||||||
|
let oauth_token = extract_oauth_token(contents, &oauth_domain);
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.oauth_token = oauth_token.clone();
|
this.oauth_token = oauth_token.clone();
|
||||||
|
@ -411,9 +468,10 @@ impl CopilotChat {
|
||||||
oauth_token: std::env::var(COPILOT_OAUTH_ENV_VAR).ok(),
|
oauth_token: std::env::var(COPILOT_OAUTH_ENV_VAR).ok(),
|
||||||
api_token: None,
|
api_token: None,
|
||||||
models: None,
|
models: None,
|
||||||
settings,
|
configuration,
|
||||||
client,
|
client,
|
||||||
};
|
};
|
||||||
|
|
||||||
if this.oauth_token.is_some() {
|
if this.oauth_token.is_some() {
|
||||||
cx.spawn(async move |this, mut cx| Self::update_models(&this, &mut cx).await)
|
cx.spawn(async move |this, mut cx| Self::update_models(&this, &mut cx).await)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
@ -423,30 +481,26 @@ impl CopilotChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_models(this: &WeakEntity<Self>, cx: &mut AsyncApp) -> Result<()> {
|
async fn update_models(this: &WeakEntity<Self>, cx: &mut AsyncApp) -> Result<()> {
|
||||||
let (oauth_token, client, auth_url) = this.read_with(cx, |this, _| {
|
let (oauth_token, client, configuration) = this.read_with(cx, |this, _| {
|
||||||
(
|
(
|
||||||
this.oauth_token.clone(),
|
this.oauth_token.clone(),
|
||||||
this.client.clone(),
|
this.client.clone(),
|
||||||
this.settings.auth_url.clone(),
|
this.configuration.clone(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let api_token = request_api_token(
|
|
||||||
&oauth_token.ok_or_else(|| {
|
|
||||||
anyhow!("OAuth token is missing while updating Copilot Chat models")
|
|
||||||
})?,
|
|
||||||
auth_url,
|
|
||||||
client.clone(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let models_url = this.update(cx, |this, cx| {
|
let oauth_token = oauth_token
|
||||||
this.api_token = Some(api_token.clone());
|
.ok_or_else(|| anyhow!("OAuth token is missing while updating Copilot Chat models"))?;
|
||||||
cx.notify();
|
|
||||||
this.settings.models_url.clone()
|
let token_url = configuration.token_url();
|
||||||
})?;
|
let api_token = request_api_token(&oauth_token, token_url.into(), client.clone()).await?;
|
||||||
let models = get_models(models_url, api_token.api_key, client.clone()).await?;
|
|
||||||
|
let models_url = configuration.models_url_from_endpoint(&api_token.api_endpoint);
|
||||||
|
let models =
|
||||||
|
get_models(models_url.into(), api_token.api_key.clone(), client.clone()).await?;
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
this.api_token = Some(api_token);
|
||||||
this.models = Some(models);
|
this.models = Some(models);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
|
@ -471,23 +525,23 @@ impl CopilotChat {
|
||||||
.flatten()
|
.flatten()
|
||||||
.context("Copilot chat is not enabled")?;
|
.context("Copilot chat is not enabled")?;
|
||||||
|
|
||||||
let (oauth_token, api_token, client, api_url, auth_url) =
|
let (oauth_token, api_token, client, configuration) = this.read_with(&cx, |this, _| {
|
||||||
this.read_with(&cx, |this, _| {
|
(
|
||||||
(
|
this.oauth_token.clone(),
|
||||||
this.oauth_token.clone(),
|
this.api_token.clone(),
|
||||||
this.api_token.clone(),
|
this.client.clone(),
|
||||||
this.client.clone(),
|
this.configuration.clone(),
|
||||||
this.settings.api_url.clone(),
|
)
|
||||||
this.settings.auth_url.clone(),
|
})?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let oauth_token = oauth_token.context("No OAuth token available")?;
|
let oauth_token = oauth_token.context("No OAuth token available")?;
|
||||||
|
|
||||||
let token = match api_token {
|
let token = match api_token {
|
||||||
Some(api_token) if api_token.remaining_seconds() > 5 * 60 => api_token.clone(),
|
Some(api_token) if api_token.remaining_seconds() > 5 * 60 => api_token.clone(),
|
||||||
_ => {
|
_ => {
|
||||||
let token = request_api_token(&oauth_token, auth_url, client.clone()).await?;
|
let token_url = configuration.token_url();
|
||||||
|
let token =
|
||||||
|
request_api_token(&oauth_token, token_url.into(), client.clone()).await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.api_token = Some(token.clone());
|
this.api_token = Some(token.clone());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -496,13 +550,19 @@ impl CopilotChat {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stream_completion(client.clone(), token.api_key, api_url, request).await
|
let api_url = configuration.api_url_from_endpoint(&token.api_endpoint);
|
||||||
|
stream_completion(client.clone(), token.api_key, api_url.into(), request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_settings(&mut self, settings: CopilotChatSettings, cx: &mut Context<Self>) {
|
pub fn set_configuration(
|
||||||
let same_settings = self.settings == settings;
|
&mut self,
|
||||||
self.settings = settings;
|
configuration: CopilotChatConfiguration,
|
||||||
if !same_settings {
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let same_configuration = self.configuration == configuration;
|
||||||
|
self.configuration = configuration;
|
||||||
|
if !same_configuration {
|
||||||
|
self.api_token = None;
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
Self::update_models(&this, cx).await?;
|
Self::update_models(&this, cx).await?;
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
|
@ -522,16 +582,12 @@ async fn get_models(
|
||||||
let mut models: Vec<Model> = all_models
|
let mut models: Vec<Model> = all_models
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|model| {
|
.filter(|model| {
|
||||||
// Ensure user has access to the model; Policy is present only for models that must be
|
|
||||||
// enabled in the GitHub dashboard
|
|
||||||
model.model_picker_enabled
|
model.model_picker_enabled
|
||||||
&& model
|
&& model
|
||||||
.policy
|
.policy
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_none_or(|policy| policy.state == "enabled")
|
.is_none_or(|policy| policy.state == "enabled")
|
||||||
})
|
})
|
||||||
// The first model from the API response, in any given family, appear to be the non-tagged
|
|
||||||
// models, which are likely the best choice (e.g. gpt-4o rather than gpt-4o-2024-11-20)
|
|
||||||
.dedup_by(|a, b| a.capabilities.family == b.capabilities.family)
|
.dedup_by(|a, b| a.capabilities.family == b.capabilities.family)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -608,12 +664,12 @@ async fn request_api_token(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_oauth_token(contents: String) -> Option<String> {
|
fn extract_oauth_token(contents: String, domain: &str) -> Option<String> {
|
||||||
serde_json::from_str::<serde_json::Value>(&contents)
|
serde_json::from_str::<serde_json::Value>(&contents)
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
v.as_object().and_then(|obj| {
|
v.as_object().and_then(|obj| {
|
||||||
obj.iter().find_map(|(key, value)| {
|
obj.iter().find_map(|(key, value)| {
|
||||||
if key.starts_with("github.com") {
|
if key.starts_with(domain) {
|
||||||
value["oauth_token"].as_str().map(|v| v.to_string())
|
value["oauth_token"].as_str().map(|v| v.to_string())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -288,6 +288,8 @@ pub struct CopilotSettings {
|
||||||
pub proxy: Option<String>,
|
pub proxy: Option<String>,
|
||||||
/// Disable certificate verification for proxy (not recommended).
|
/// Disable certificate verification for proxy (not recommended).
|
||||||
pub proxy_no_verify: Option<bool>,
|
pub proxy_no_verify: Option<bool>,
|
||||||
|
/// Enterprise URI for Copilot.
|
||||||
|
pub enterprise_uri: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The settings for all languages.
|
/// The settings for all languages.
|
||||||
|
@ -607,6 +609,11 @@ pub struct CopilotSettingsContent {
|
||||||
/// Default: false
|
/// Default: false
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub proxy_no_verify: Option<bool>,
|
pub proxy_no_verify: Option<bool>,
|
||||||
|
/// Enterprise URI for Copilot.
|
||||||
|
///
|
||||||
|
/// Default: none
|
||||||
|
#[serde(default)]
|
||||||
|
pub enterprise_uri: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The settings for enabling/disabling features.
|
/// The settings for enabling/disabling features.
|
||||||
|
@ -1228,10 +1235,10 @@ impl settings::Settings for AllLanguageSettings {
|
||||||
let mut copilot_settings = default_value
|
let mut copilot_settings = default_value
|
||||||
.edit_predictions
|
.edit_predictions
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|settings| settings.copilot.clone())
|
.map(|settings| CopilotSettings {
|
||||||
.map(|copilot| CopilotSettings {
|
proxy: settings.copilot.proxy.clone(),
|
||||||
proxy: copilot.proxy,
|
proxy_no_verify: settings.copilot.proxy_no_verify,
|
||||||
proxy_no_verify: copilot.proxy_no_verify,
|
enterprise_uri: settings.copilot.enterprise_uri.clone(),
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
@ -1287,6 +1294,14 @@ impl settings::Settings for AllLanguageSettings {
|
||||||
copilot_settings.proxy_no_verify = Some(proxy_no_verify);
|
copilot_settings.proxy_no_verify = Some(proxy_no_verify);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(enterprise_uri) = user_settings
|
||||||
|
.edit_predictions
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|settings| settings.copilot.enterprise_uri.clone())
|
||||||
|
{
|
||||||
|
copilot_settings.enterprise_uri = Some(enterprise_uri);
|
||||||
|
}
|
||||||
|
|
||||||
// A user's global settings override the default global settings and
|
// A user's global settings override the default global settings and
|
||||||
// all default language-specific settings.
|
// all default language-specific settings.
|
||||||
merge_settings(&mut defaults, &user_settings.defaults);
|
merge_settings(&mut defaults, &user_settings.defaults);
|
||||||
|
|
|
@ -58,6 +58,7 @@ ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
zed_llm_client.workspace = true
|
zed_llm_client.workspace = true
|
||||||
|
language.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -10,15 +10,14 @@ use copilot::copilot_chat::{
|
||||||
ToolCall,
|
ToolCall,
|
||||||
};
|
};
|
||||||
use copilot::{Copilot, Status};
|
use copilot::{Copilot, Status};
|
||||||
use editor::{Editor, EditorElement, EditorStyle};
|
|
||||||
use fs::Fs;
|
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use futures::stream::BoxStream;
|
use futures::stream::BoxStream;
|
||||||
use futures::{FutureExt, Stream, StreamExt};
|
use futures::{FutureExt, Stream, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, Animation, AnimationExt, AnyView, App, AsyncApp, Entity, FontStyle, Render,
|
Action, Animation, AnimationExt, AnyView, App, AsyncApp, Entity, Render, Subscription, Task,
|
||||||
Subscription, Task, TextStyle, Transformation, WhiteSpace, percentage, svg,
|
Transformation, percentage, svg,
|
||||||
};
|
};
|
||||||
|
use language::language_settings::all_language_settings;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||||
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
||||||
|
@ -27,18 +26,14 @@ use language_model::{
|
||||||
LanguageModelToolSchemaFormat, LanguageModelToolUse, MessageContent, RateLimiter, Role,
|
LanguageModelToolSchemaFormat, LanguageModelToolUse, MessageContent, RateLimiter, Role,
|
||||||
StopReason,
|
StopReason,
|
||||||
};
|
};
|
||||||
use settings::{Settings, SettingsStore, update_settings_file};
|
use settings::SettingsStore;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use theme::ThemeSettings;
|
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::debug_panic;
|
use util::debug_panic;
|
||||||
|
|
||||||
use crate::{AllLanguageModelSettings, CopilotChatSettingsContent};
|
|
||||||
|
|
||||||
use super::anthropic::count_anthropic_tokens;
|
use super::anthropic::count_anthropic_tokens;
|
||||||
use super::google::count_google_tokens;
|
use super::google::count_google_tokens;
|
||||||
use super::open_ai::count_open_ai_tokens;
|
use super::open_ai::count_open_ai_tokens;
|
||||||
pub(crate) use copilot::copilot_chat::CopilotChatSettings;
|
|
||||||
|
|
||||||
const PROVIDER_ID: &str = "copilot_chat";
|
const PROVIDER_ID: &str = "copilot_chat";
|
||||||
const PROVIDER_NAME: &str = "GitHub Copilot Chat";
|
const PROVIDER_NAME: &str = "GitHub Copilot Chat";
|
||||||
|
@ -69,11 +64,16 @@ impl CopilotChatLanguageModelProvider {
|
||||||
_copilot_chat_subscription: copilot_chat_subscription,
|
_copilot_chat_subscription: copilot_chat_subscription,
|
||||||
_settings_subscription: cx.observe_global::<SettingsStore>(|_, cx| {
|
_settings_subscription: cx.observe_global::<SettingsStore>(|_, cx| {
|
||||||
if let Some(copilot_chat) = CopilotChat::global(cx) {
|
if let Some(copilot_chat) = CopilotChat::global(cx) {
|
||||||
let settings = AllLanguageModelSettings::get_global(cx)
|
let language_settings = all_language_settings(None, cx);
|
||||||
.copilot_chat
|
let configuration = copilot::copilot_chat::CopilotChatConfiguration {
|
||||||
.clone();
|
enterprise_uri: language_settings
|
||||||
|
.edit_predictions
|
||||||
|
.copilot
|
||||||
|
.enterprise_uri
|
||||||
|
.clone(),
|
||||||
|
};
|
||||||
copilot_chat.update(cx, |chat, cx| {
|
copilot_chat.update(cx, |chat, cx| {
|
||||||
chat.set_settings(settings, cx);
|
chat.set_configuration(configuration, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -174,10 +174,9 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider {
|
||||||
Task::ready(Err(err.into()))
|
Task::ready(Err(err.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView {
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
cx.new(|cx| ConfigurationView::new(state, window, cx))
|
cx.new(|cx| ConfigurationView::new(state, cx)).into()
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> {
|
fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> {
|
||||||
|
@ -622,38 +621,15 @@ fn into_copilot_chat(
|
||||||
|
|
||||||
struct ConfigurationView {
|
struct ConfigurationView {
|
||||||
copilot_status: Option<copilot::Status>,
|
copilot_status: Option<copilot::Status>,
|
||||||
api_url_editor: Entity<Editor>,
|
|
||||||
models_url_editor: Entity<Editor>,
|
|
||||||
auth_url_editor: Entity<Editor>,
|
|
||||||
state: Entity<State>,
|
state: Entity<State>,
|
||||||
_subscription: Option<Subscription>,
|
_subscription: Option<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurationView {
|
impl ConfigurationView {
|
||||||
pub fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
pub fn new(state: Entity<State>, cx: &mut Context<Self>) -> Self {
|
||||||
let copilot = Copilot::global(cx);
|
let copilot = Copilot::global(cx);
|
||||||
let settings = AllLanguageModelSettings::get_global(cx)
|
|
||||||
.copilot_chat
|
|
||||||
.clone();
|
|
||||||
let api_url_editor = cx.new(|cx| Editor::single_line(window, cx));
|
|
||||||
api_url_editor.update(cx, |this, cx| {
|
|
||||||
this.set_text(settings.api_url.clone(), window, cx);
|
|
||||||
this.set_placeholder_text("GitHub Copilot API URL", cx);
|
|
||||||
});
|
|
||||||
let models_url_editor = cx.new(|cx| Editor::single_line(window, cx));
|
|
||||||
models_url_editor.update(cx, |this, cx| {
|
|
||||||
this.set_text(settings.models_url.clone(), window, cx);
|
|
||||||
this.set_placeholder_text("GitHub Copilot Models URL", cx);
|
|
||||||
});
|
|
||||||
let auth_url_editor = cx.new(|cx| Editor::single_line(window, cx));
|
|
||||||
auth_url_editor.update(cx, |this, cx| {
|
|
||||||
this.set_text(settings.auth_url.clone(), window, cx);
|
|
||||||
this.set_placeholder_text("GitHub Copilot Auth URL", cx);
|
|
||||||
});
|
|
||||||
Self {
|
Self {
|
||||||
api_url_editor,
|
|
||||||
models_url_editor,
|
|
||||||
auth_url_editor,
|
|
||||||
copilot_status: copilot.as_ref().map(|copilot| copilot.read(cx).status()),
|
copilot_status: copilot.as_ref().map(|copilot| copilot.read(cx).status()),
|
||||||
state,
|
state,
|
||||||
_subscription: copilot.as_ref().map(|copilot| {
|
_subscription: copilot.as_ref().map(|copilot| {
|
||||||
|
@ -664,104 +640,6 @@ impl ConfigurationView {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn make_input_styles(&self, cx: &App) -> Div {
|
|
||||||
let bg_color = cx.theme().colors().editor_background;
|
|
||||||
let border_color = cx.theme().colors().border;
|
|
||||||
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.px_2()
|
|
||||||
.py_1()
|
|
||||||
.bg(bg_color)
|
|
||||||
.border_1()
|
|
||||||
.border_color(border_color)
|
|
||||||
.rounded_sm()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_text_style(&self, cx: &Context<Self>) -> TextStyle {
|
|
||||||
let settings = ThemeSettings::get_global(cx);
|
|
||||||
TextStyle {
|
|
||||||
color: cx.theme().colors().text,
|
|
||||||
font_family: settings.ui_font.family.clone(),
|
|
||||||
font_features: settings.ui_font.features.clone(),
|
|
||||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
|
||||||
font_size: rems(0.875).into(),
|
|
||||||
font_weight: settings.ui_font.weight,
|
|
||||||
font_style: FontStyle::Normal,
|
|
||||||
line_height: relative(1.3),
|
|
||||||
background_color: None,
|
|
||||||
underline: None,
|
|
||||||
strikethrough: None,
|
|
||||||
white_space: WhiteSpace::Normal,
|
|
||||||
text_overflow: None,
|
|
||||||
text_align: Default::default(),
|
|
||||||
line_clamp: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_api_url_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let text_style = self.make_text_style(cx);
|
|
||||||
|
|
||||||
EditorElement::new(
|
|
||||||
&self.api_url_editor,
|
|
||||||
EditorStyle {
|
|
||||||
background: cx.theme().colors().editor_background,
|
|
||||||
local_player: cx.theme().players().local(),
|
|
||||||
text: text_style,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_auth_url_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let text_style = self.make_text_style(cx);
|
|
||||||
|
|
||||||
EditorElement::new(
|
|
||||||
&self.auth_url_editor,
|
|
||||||
EditorStyle {
|
|
||||||
background: cx.theme().colors().editor_background,
|
|
||||||
local_player: cx.theme().players().local(),
|
|
||||||
text: text_style,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn render_models_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let text_style = self.make_text_style(cx);
|
|
||||||
|
|
||||||
EditorElement::new(
|
|
||||||
&self.models_url_editor,
|
|
||||||
EditorStyle {
|
|
||||||
background: cx.theme().colors().editor_background,
|
|
||||||
local_player: cx.theme().players().local(),
|
|
||||||
text: text_style,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_copilot_settings(&self, cx: &mut Context<'_, Self>) {
|
|
||||||
let settings = CopilotChatSettings {
|
|
||||||
api_url: self.api_url_editor.read(cx).text(cx).into(),
|
|
||||||
models_url: self.models_url_editor.read(cx).text(cx).into(),
|
|
||||||
auth_url: self.auth_url_editor.read(cx).text(cx).into(),
|
|
||||||
};
|
|
||||||
update_settings_file::<AllLanguageModelSettings>(<dyn Fs>::global(cx), cx, {
|
|
||||||
let settings = settings.clone();
|
|
||||||
move |content, _| {
|
|
||||||
content.copilot_chat = Some(CopilotChatSettingsContent {
|
|
||||||
api_url: Some(settings.api_url.as_ref().into()),
|
|
||||||
models_url: Some(settings.models_url.as_ref().into()),
|
|
||||||
auth_url: Some(settings.auth_url.as_ref().into()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(chat) = CopilotChat::global(cx) {
|
|
||||||
chat.update(cx, |this, cx| {
|
|
||||||
this.set_settings(settings, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ConfigurationView {
|
impl Render for ConfigurationView {
|
||||||
|
@ -819,59 +697,15 @@ impl Render for ConfigurationView {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
const LABEL: &str = "To use Zed's assistant with GitHub Copilot, you need to be logged in to GitHub. Note that your GitHub account must have an active Copilot Chat subscription.";
|
const LABEL: &str = "To use Zed's assistant with GitHub Copilot, you need to be logged in to GitHub. Note that your GitHub account must have an active Copilot Chat subscription.";
|
||||||
v_flex()
|
v_flex().gap_2().child(Label::new(LABEL)).child(
|
||||||
.gap_2()
|
Button::new("sign_in", "Sign in to use GitHub Copilot")
|
||||||
.child(Label::new(LABEL))
|
.icon_color(Color::Muted)
|
||||||
.on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
|
.icon(IconName::Github)
|
||||||
this.update_copilot_settings(cx);
|
.icon_position(IconPosition::Start)
|
||||||
copilot::initiate_sign_in(window, cx);
|
.icon_size(IconSize::Medium)
|
||||||
}))
|
.full_width()
|
||||||
.child(
|
.on_click(|_, window, cx| copilot::initiate_sign_in(window, cx)),
|
||||||
v_flex()
|
)
|
||||||
.gap_0p5()
|
|
||||||
.child(Label::new("API URL").size(LabelSize::Small))
|
|
||||||
.child(
|
|
||||||
self.make_input_styles(cx)
|
|
||||||
.child(self.render_api_url_editor(cx)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_0p5()
|
|
||||||
.child(Label::new("Auth URL").size(LabelSize::Small))
|
|
||||||
.child(
|
|
||||||
self.make_input_styles(cx)
|
|
||||||
.child(self.render_auth_url_editor(cx)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_0p5()
|
|
||||||
.child(Label::new("Models list URL").size(LabelSize::Small))
|
|
||||||
.child(
|
|
||||||
self.make_input_styles(cx)
|
|
||||||
.child(self.render_models_editor(cx)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("sign_in", "Sign in to use GitHub Copilot")
|
|
||||||
.icon_color(Color::Muted)
|
|
||||||
.icon(IconName::Github)
|
|
||||||
.icon_position(IconPosition::Start)
|
|
||||||
.icon_size(IconSize::Medium)
|
|
||||||
.full_width()
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
this.update_copilot_settings(cx);
|
|
||||||
copilot::initiate_sign_in(window, cx)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new(
|
|
||||||
format!("You can also assign the {} environment variable and restart Zed.", copilot::copilot_chat::COPILOT_OAUTH_ENV_VAR),
|
|
||||||
)
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => v_flex().gap_6().child(Label::new(ERROR_LABEL)),
|
None => v_flex().gap_6().child(Label::new(ERROR_LABEL)),
|
||||||
|
|
|
@ -13,7 +13,6 @@ use crate::provider::{
|
||||||
anthropic::AnthropicSettings,
|
anthropic::AnthropicSettings,
|
||||||
bedrock::AmazonBedrockSettings,
|
bedrock::AmazonBedrockSettings,
|
||||||
cloud::{self, ZedDotDevSettings},
|
cloud::{self, ZedDotDevSettings},
|
||||||
copilot_chat::CopilotChatSettings,
|
|
||||||
deepseek::DeepSeekSettings,
|
deepseek::DeepSeekSettings,
|
||||||
google::GoogleSettings,
|
google::GoogleSettings,
|
||||||
lmstudio::LmStudioSettings,
|
lmstudio::LmStudioSettings,
|
||||||
|
@ -65,7 +64,7 @@ pub struct AllLanguageModelSettings {
|
||||||
pub open_router: OpenRouterSettings,
|
pub open_router: OpenRouterSettings,
|
||||||
pub zed_dot_dev: ZedDotDevSettings,
|
pub zed_dot_dev: ZedDotDevSettings,
|
||||||
pub google: GoogleSettings,
|
pub google: GoogleSettings,
|
||||||
pub copilot_chat: CopilotChatSettings,
|
|
||||||
pub lmstudio: LmStudioSettings,
|
pub lmstudio: LmStudioSettings,
|
||||||
pub deepseek: DeepSeekSettings,
|
pub deepseek: DeepSeekSettings,
|
||||||
pub mistral: MistralSettings,
|
pub mistral: MistralSettings,
|
||||||
|
@ -83,7 +82,7 @@ pub struct AllLanguageModelSettingsContent {
|
||||||
pub zed_dot_dev: Option<ZedDotDevSettingsContent>,
|
pub zed_dot_dev: Option<ZedDotDevSettingsContent>,
|
||||||
pub google: Option<GoogleSettingsContent>,
|
pub google: Option<GoogleSettingsContent>,
|
||||||
pub deepseek: Option<DeepseekSettingsContent>,
|
pub deepseek: Option<DeepseekSettingsContent>,
|
||||||
pub copilot_chat: Option<CopilotChatSettingsContent>,
|
|
||||||
pub mistral: Option<MistralSettingsContent>,
|
pub mistral: Option<MistralSettingsContent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,13 +270,6 @@ pub struct ZedDotDevSettingsContent {
|
||||||
available_models: Option<Vec<cloud::AvailableModel>>,
|
available_models: Option<Vec<cloud::AvailableModel>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
|
|
||||||
pub struct CopilotChatSettingsContent {
|
|
||||||
pub api_url: Option<String>,
|
|
||||||
pub auth_url: Option<String>,
|
|
||||||
pub models_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
|
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
|
||||||
pub struct OpenRouterSettingsContent {
|
pub struct OpenRouterSettingsContent {
|
||||||
pub api_url: Option<String>,
|
pub api_url: Option<String>,
|
||||||
|
@ -435,24 +427,6 @@ impl settings::Settings for AllLanguageModelSettings {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|s| s.available_models.clone()),
|
.and_then(|s| s.available_models.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Copilot Chat
|
|
||||||
let copilot_chat = value.copilot_chat.clone().unwrap_or_default();
|
|
||||||
|
|
||||||
settings.copilot_chat.api_url = copilot_chat.api_url.map_or_else(
|
|
||||||
|| Arc::from("https://api.githubcopilot.com/chat/completions"),
|
|
||||||
Arc::from,
|
|
||||||
);
|
|
||||||
|
|
||||||
settings.copilot_chat.auth_url = copilot_chat.auth_url.map_or_else(
|
|
||||||
|| Arc::from("https://api.github.com/copilot_internal/v2/token"),
|
|
||||||
Arc::from,
|
|
||||||
);
|
|
||||||
|
|
||||||
settings.copilot_chat.models_url = copilot_chat.models_url.map_or_else(
|
|
||||||
|| Arc::from("https://api.githubcopilot.com/models"),
|
|
||||||
Arc::from,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(settings)
|
Ok(settings)
|
||||||
|
|
|
@ -4293,7 +4293,12 @@ mod tests {
|
||||||
project_panel::init(cx);
|
project_panel::init(cx);
|
||||||
outline_panel::init(cx);
|
outline_panel::init(cx);
|
||||||
terminal_view::init(cx);
|
terminal_view::init(cx);
|
||||||
copilot::copilot_chat::init(app_state.fs.clone(), app_state.client.http_client(), cx);
|
copilot::copilot_chat::init(
|
||||||
|
app_state.fs.clone(),
|
||||||
|
app_state.client.http_client(),
|
||||||
|
copilot::copilot_chat::CopilotChatConfiguration::default(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
image_viewer::init(cx);
|
image_viewer::init(cx);
|
||||||
language_model::init(app_state.client.clone(), cx);
|
language_model::init(app_state.client.clone(), cx);
|
||||||
language_models::init(
|
language_models::init(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue