copilot: Support HTTP/HTTPS proxy for Copilot language server (#24364)

Closes #6701 (one of the top ranking issues as of writing)

Adds the ability to specify an HTTP/HTTPS proxy to route Copilot code
completion API requests through. This should fix copilot functionality
in restricted network environments (where such a proxy is required) but
also opens up the ability to point copilot code completion requests at
your own local LLM, using e.g.:
- https://github.com/jjleng/copilot-proxy
- https://github.com/bernardo-bruning/ollama-copilot/tree/master

External MITM-proxy tools permitting, this can serve as a stop-gap to
allow local LLM code completion in Zed until a proper OpenAI-compatible
local code completions provider is implemented. With this in mind, in
this PR I've added separate `settings.json` variables to configure a
proxy server _specific to the code completions provider_ instead of
using the global `proxy` setting, to allow for cases like this where we
_only_ want to proxy e.g. the Copilot requests, but not all outgoing
traffic from the application.

Currently, two new settings are added:
- `inline_completions.copilot.proxy`: Proxy server URL (HTTP and HTTPS
schemes supported)
- `inline_completions.copilot.proxy_no_verify`: Whether to disable
certificate verification through the proxy

Example:
```js
"features": {
  "inline_completion_provider": "copilot"
},
"show_completions_on_input": true,
// New:
"inline_completions": {
  "copilot": {
    "proxy": "http://example.com:15432",
    "proxy_no_verify": true
  }
}
```


Release Notes:

- Added the ability to specify an HTTP/HTTPS proxy for Copilot.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
This commit is contained in:
Eli Kaplan 2025-02-24 09:11:00 -08:00 committed by GitHub
parent dd0de3cfa9
commit a8d56877ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 102 additions and 7 deletions

View file

@ -234,6 +234,8 @@ pub struct EditPredictionSettings {
pub disabled_globs: Vec<GlobMatcher>,
/// Configures how edit predictions are displayed in the buffer.
pub mode: EditPredictionsMode,
/// Settings specific to GitHub Copilot.
pub copilot: CopilotSettings,
}
/// The mode in which edit predictions should be displayed.
@ -248,6 +250,14 @@ pub enum EditPredictionsMode {
EagerPreview,
}
#[derive(Clone, Debug, Default)]
pub struct CopilotSettings {
/// HTTP/HTTPS proxy to use for Copilot.
pub proxy: Option<String>,
/// Disable certificate verification for proxy (not recommended).
pub proxy_no_verify: Option<bool>,
}
/// The settings for all languages.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AllLanguageSettingsContent {
@ -465,6 +475,23 @@ pub struct EditPredictionSettingsContent {
/// Provider support required.
#[serde(default)]
pub mode: EditPredictionsMode,
/// Settings specific to GitHub Copilot.
#[serde(default)]
pub copilot: CopilotSettingsContent,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct CopilotSettingsContent {
/// HTTP/HTTPS proxy to use for Copilot.
///
/// Default: none
#[serde(default)]
pub proxy: Option<String>,
/// Disable certificate verification for the proxy (not recommended).
///
/// Default: false
#[serde(default)]
pub proxy_no_verify: Option<bool>,
}
/// The settings for enabling/disabling features.
@ -1064,6 +1091,16 @@ impl settings::Settings for AllLanguageSettings {
.map(|globs| globs.iter().collect())
.ok_or_else(Self::missing_default)?;
let mut copilot_settings = default_value
.edit_predictions
.as_ref()
.map(|settings| settings.copilot.clone())
.map(|copilot| CopilotSettings {
proxy: copilot.proxy,
proxy_no_verify: copilot.proxy_no_verify,
})
.unwrap_or_default();
let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default();
for (language, suffixes) in &default_value.file_types {
@ -1096,6 +1133,22 @@ impl settings::Settings for AllLanguageSettings {
}
}
if let Some(proxy) = user_settings
.edit_predictions
.as_ref()
.and_then(|settings| settings.copilot.proxy.clone())
{
copilot_settings.proxy = Some(proxy);
}
if let Some(proxy_no_verify) = user_settings
.edit_predictions
.as_ref()
.and_then(|settings| settings.copilot.proxy_no_verify)
{
copilot_settings.proxy_no_verify = Some(proxy_no_verify);
}
// A user's global settings override the default global settings and
// all default language-specific settings.
merge_settings(&mut defaults, &user_settings.defaults);
@ -1147,6 +1200,7 @@ impl settings::Settings for AllLanguageSettings {
.filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
.collect(),
mode: edit_predictions_mode,
copilot: copilot_settings,
},
defaults,
languages,