@@ -62,12 +62,6 @@ impl CopilotChatConfiguration {
}
}
-// Copilot's base model; defined by Microsoft in premium requests table
-// This will be moved to the front of the Copilot model list, and will be used for
-// 'fast' requests (e.g. title generation)
-// https://docs.github.com/en/copilot/managing-copilot/monitoring-usage-and-entitlements/about-premium-requests
-const DEFAULT_MODEL_ID: &str = "gpt-4.1";
-
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Role {
@@ -101,22 +95,39 @@ where
Ok(models)
}
-#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
+#[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,
}
+#[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,
}
#[derive(Default, Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
@@ -602,6 +613,7 @@ async fn get_models(
.into_iter()
.filter(|model| {
model.model_picker_enabled
+ && model.capabilities.model_type.as_str() == "chat"
&& model
.policy
.as_ref()
@@ -610,9 +622,7 @@ async fn get_models(
.dedup_by(|a, b| a.capabilities.family == b.capabilities.family)
.collect();
- if let Some(default_model_position) =
- models.iter().position(|model| model.id == DEFAULT_MODEL_ID)
- {
+ if let Some(default_model_position) = models.iter().position(|model| model.is_chat_default) {
let default_model = models.remove(default_model_position);
models.insert(0, default_model);
}
@@ -630,7 +640,9 @@ async fn request_models(
.uri(models_url.as_ref())
.header("Authorization", format!("Bearer {}", api_token))
.header("Content-Type", "application/json")
- .header("Copilot-Integration-Id", "vscode-chat");
+ .header("Copilot-Integration-Id", "vscode-chat")
+ .header("Editor-Version", "vscode/1.103.2")
+ .header("x-github-api-version", "2025-05-01");
let request = request_builder.body(AsyncBody::empty())?;
@@ -801,6 +813,10 @@ mod tests {
let json = r#"{
"data": [
{
+ "billing": {
+ "is_premium": false,
+ "multiplier": 0
+ },
"capabilities": {
"family": "gpt-4",
"limits": {
@@ -814,6 +830,8 @@ mod tests {
"type": "chat"
},
"id": "gpt-4",
+ "is_chat_default": false,
+ "is_chat_fallback": false,
"model_picker_enabled": false,
"name": "GPT 4",
"object": "model",
@@ -825,6 +843,16 @@ mod tests {
"some-unknown-field": 123
},
{
+ "billing": {
+ "is_premium": true,
+ "multiplier": 1,
+ "restricted_to": [
+ "pro",
+ "pro_plus",
+ "business",
+ "enterprise"
+ ]
+ },
"capabilities": {
"family": "claude-3.7-sonnet",
"limits": {
@@ -848,6 +876,8 @@ mod tests {
"type": "chat"
},
"id": "claude-3.7-sonnet",
+ "is_chat_default": false,
+ "is_chat_fallback": false,
"model_picker_enabled": true,
"name": "Claude 3.7 Sonnet",
"object": "model",