Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c7e7767
Initial Claude Opus 4.5 implementation of Provider Extensions
rtfeldman Dec 2, 2025
d7caae3
Fix auth and subscriptions for provider extensions
rtfeldman Dec 3, 2025
e5ce32e
Add provider extension API key in settings
rtfeldman Dec 3, 2025
04de456
Use extension-llm- prefix for credential keys
rtfeldman Dec 3, 2025
948905d
Revise provider extensions for Gemini API
rtfeldman Dec 4, 2025
b200e10
Clean up debug statements
rtfeldman Dec 4, 2025
b0767c1
Merge remote-tracking branch 'origin/main' into provider-extensions
rtfeldman Dec 4, 2025
a95f3f3
Clean up debug logging
rtfeldman Dec 4, 2025
e08ab99
Add extensions for LLM providers
rtfeldman Dec 4, 2025
5820732
restore impl Drop for WasmExtension
rtfeldman Dec 4, 2025
2a89529
Use named fields
rtfeldman Dec 4, 2025
f54e7f8
Add trailing newlines
rtfeldman Dec 4, 2025
fcb3d3d
Update a comment
rtfeldman Dec 4, 2025
1396c68
Add svg icons to llm provider extensions
rtfeldman Dec 4, 2025
63c35d2
Use local icons in llm extensions
rtfeldman Dec 4, 2025
bf2b8e9
use fill=black over fill=currentColor
rtfeldman Dec 4, 2025
fec9525
Add env var checkbox
rtfeldman Dec 4, 2025
a48bd10
Add llm extensions to auto_install_extensions
rtfeldman Dec 4, 2025
2d3a352
Add OAuth Web Flow auth option for llm provider extensions
rtfeldman Dec 4, 2025
aabed94
Add OAuth via web authentication to llm extensions, migrate copilot
rtfeldman Dec 4, 2025
3b6b3ff
Specify env vars for the builtin extensions
rtfeldman Dec 4, 2025
e1a9269
Delete example provider extension
rtfeldman Dec 4, 2025
5559726
Remove builtin extensions for now
rtfeldman Dec 4, 2025
8b1ce75
Move wit extensions into their own module
rtfeldman Dec 5, 2025
2031ca1
Revert auto-install extensions for now
rtfeldman Dec 5, 2025
21de6d3
Revert "Revert auto-install extensions for now"
rtfeldman Dec 5, 2025
ccd6672
Revert "Remove builtin extensions for now"
rtfeldman Dec 5, 2025
a0d3bc3
Rename copilot_chat to copilot-chat
rtfeldman Dec 5, 2025
4464392
Use kebab-case for open-router extension too.
rtfeldman Dec 5, 2025
8b5b271
Update Cargo.lock
rtfeldman Dec 5, 2025
a198b6c
Use icon in more places
rtfeldman Dec 5, 2025
b1934fb
Remove builtin Anthropic provider
rtfeldman Dec 5, 2025
7183b8a
Fix API key bug
rtfeldman Dec 5, 2025
cc5f5e3
Clean up some comments
rtfeldman Dec 5, 2025
d1e7739
Don't make v0.8.0 available on Stable/Preview yet
maxdeviant Dec 5, 2025
e2b49b3
Restore blank lines from `main`
maxdeviant Dec 5, 2025
c999854
Revert spurious changes to `default.json`
maxdeviant Dec 5, 2025
b90ac2d
Fix Drop impl for WasmExtension
rtfeldman Dec 5, 2025
c89653b
Fix bugs around logging out from provider extensions
rtfeldman Dec 5, 2025
f326b00
Sign in button for Copilot
rtfeldman Dec 5, 2025
592985a
More Copilot Chat auth
rtfeldman Dec 5, 2025
3a2010f
Attempt web-based oauth flow for Copilot
rtfeldman Dec 6, 2025
ef0b0ed
Switch to device flow oauth for copilot
rtfeldman Dec 6, 2025
9248955
Show device code in provider auth
rtfeldman Dec 6, 2025
dd7ee50
Make the "Copy Code" button nicer
rtfeldman Dec 6, 2025
e990ea4
Dynamically fetch Copilot model list
rtfeldman Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion crates/acp_thread/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,21 @@ pub trait AgentModelSelector: 'static {
}
}

/// Icon for a model in the model selector.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AgentModelIcon {
/// A built-in icon from Zed's icon set.
Named(IconName),
/// Path to a custom SVG icon file.
Path(SharedString),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentModelInfo {
pub id: acp::ModelId,
pub name: SharedString,
pub description: Option<SharedString>,
pub icon: Option<IconName>,
pub icon: Option<AgentModelIcon>,
}

impl From<acp::ModelInfo> for AgentModelInfo {
Expand Down
11 changes: 8 additions & 3 deletions crates/agent/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub use templates::*;
pub use thread::*;
pub use tools::*;

use acp_thread::{AcpThread, AgentModelSelector};
use acp_thread::{AcpThread, AgentModelIcon, AgentModelSelector};
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use chrono::{DateTime, Utc};
Expand Down Expand Up @@ -161,11 +161,16 @@ impl LanguageModels {
model: &Arc<dyn LanguageModel>,
provider: &Arc<dyn LanguageModelProvider>,
) -> acp_thread::AgentModelInfo {
let icon = if let Some(path) = provider.icon_path() {
Some(AgentModelIcon::Path(path))
} else {
Some(AgentModelIcon::Named(provider.icon()))
};
acp_thread::AgentModelInfo {
id: Self::model_id(model),
name: model.name().0,
description: None,
icon: Some(provider.icon()),
icon,
}
}

Expand Down Expand Up @@ -1356,7 +1361,7 @@ mod internal_tests {
id: acp::ModelId::new("fake/fake"),
name: "Fake".into(),
description: None,
icon: Some(ui::IconName::ZedAssistant),
icon: Some(AgentModelIcon::Named(ui::IconName::ZedAssistant)),
}]
)])
);
Expand Down
18 changes: 12 additions & 6 deletions crates/agent_ui/src/acp/model_selector.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{cmp::Reverse, rc::Rc, sync::Arc};

use acp_thread::{AgentModelInfo, AgentModelList, AgentModelSelector};
use acp_thread::{AgentModelIcon, AgentModelInfo, AgentModelList, AgentModelSelector};
use agent_servers::AgentServer;
use anyhow::Result;
use collections::IndexMap;
Expand Down Expand Up @@ -292,12 +292,18 @@ impl PickerDelegate for AcpModelPickerDelegate {
h_flex()
.w_full()
.gap_1p5()
.when_some(model_info.icon, |this, icon| {
this.child(
Icon::new(icon)
.map(|this| match &model_info.icon {
Some(AgentModelIcon::Path(path)) => this.child(
Icon::from_path(path.clone())
.color(model_icon_color)
.size(IconSize::Small)
)
.size(IconSize::Small),
),
Some(AgentModelIcon::Named(icon)) => this.child(
Icon::new(*icon)
.color(model_icon_color)
.size(IconSize::Small),
),
None => this,
})
.child(Label::new(model_info.name.clone()).truncate()),
)
Expand Down
13 changes: 9 additions & 4 deletions crates/agent_ui/src/acp/model_selector_popover.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::rc::Rc;
use std::sync::Arc;

use acp_thread::{AgentModelInfo, AgentModelSelector};
use acp_thread::{AgentModelIcon, AgentModelInfo, AgentModelSelector};
use agent_servers::AgentServer;
use fs::Fs;
use gpui::{Entity, FocusHandle};
Expand Down Expand Up @@ -64,7 +64,7 @@ impl Render for AcpModelSelectorPopover {
.map(|model| model.name.clone())
.unwrap_or_else(|| SharedString::from("Select a Model"));

let model_icon = model.as_ref().and_then(|model| model.icon);
let model_icon = model.as_ref().and_then(|model| model.icon.clone());

let focus_handle = self.focus_handle.clone();

Expand All @@ -78,8 +78,13 @@ impl Render for AcpModelSelectorPopover {
self.selector.clone(),
ButtonLike::new("active-model")
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.when_some(model_icon, |this, icon| {
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
.when_some(model_icon, |this, icon| match icon {
AgentModelIcon::Path(path) => {
this.child(Icon::from_path(path).color(color).size(IconSize::XSmall))
}
AgentModelIcon::Named(icon_name) => {
this.child(Icon::new(icon_name).color(color).size(IconSize::XSmall))
}
})
.child(
Label::new(model_name)
Expand Down
10 changes: 7 additions & 3 deletions crates/agent_ui/src/agent_configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,15 @@ impl AgentConfiguration {
h_flex()
.w_full()
.gap_1p5()
.child(
.child(if let Some(icon_path) = provider.icon_path() {
Icon::from_external_svg(icon_path)
.size(IconSize::Small)
.color(Color::Muted)
} else {
Icon::new(provider.icon())
.size(IconSize::Small)
.color(Color::Muted),
)
.color(Color::Muted)
})
.child(
h_flex()
.w_full()
Expand Down
16 changes: 13 additions & 3 deletions crates/agent_ui/src/agent_model_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ impl Render for AgentModelSelector {
.map(|model| model.model.name().0)
.unwrap_or_else(|| SharedString::from("Select a Model"));

let provider_icon = model.as_ref().map(|model| model.provider.icon());
let provider_icon_path = model.as_ref().and_then(|model| model.provider.icon_path());
let provider_icon_name = model.as_ref().map(|model| model.provider.icon());
let color = if self.menu_handle.is_deployed() {
Color::Accent
} else {
Expand All @@ -85,8 +86,17 @@ impl Render for AgentModelSelector {
PickerPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.when_some(provider_icon, |this, icon| {
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
.when_some(provider_icon_path.clone(), |this, icon_path| {
this.child(
Icon::from_external_svg(icon_path)
.color(color)
.size(IconSize::XSmall),
)
})
.when(provider_icon_path.is_none(), |this| {
this.when_some(provider_icon_name, |this, icon| {
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
})
})
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.child(
Expand Down
37 changes: 32 additions & 5 deletions crates/agent_ui/src/agent_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,13 @@ fn init_language_model_settings(cx: &mut App) {
cx.subscribe(
&LanguageModelRegistry::global(cx),
|_, event: &language_model::Event, cx| match event {
language_model::Event::ProviderStateChanged(_)
| language_model::Event::AddedProvider(_)
| language_model::Event::RemovedProvider(_) => {
language_model::Event::ProviderStateChanged(_) => {
update_active_language_model_from_settings(cx);
}
language_model::Event::AddedProvider(_) => {
update_active_language_model_from_settings(cx);
}
language_model::Event::RemovedProvider(_) => {
update_active_language_model_from_settings(cx);
}
_ => {}
Expand All @@ -367,26 +371,49 @@ fn update_active_language_model_from_settings(cx: &mut App) {
}
}

let default = settings.default_model.as_ref().map(to_selected_model);
// Filter out models from providers that are not authenticated
fn is_provider_authenticated(
selection: &LanguageModelSelection,
registry: &LanguageModelRegistry,
cx: &App,
) -> bool {
let provider_id = LanguageModelProviderId::from(selection.provider.0.clone());
registry
.provider(&provider_id)
.map_or(false, |provider| provider.is_authenticated(cx))
}

let registry = LanguageModelRegistry::global(cx);
let registry_ref = registry.read(cx);

let default = settings
.default_model
.as_ref()
.filter(|s| is_provider_authenticated(s, registry_ref, cx))
.map(to_selected_model);
let inline_assistant = settings
.inline_assistant_model
.as_ref()
.filter(|s| is_provider_authenticated(s, registry_ref, cx))
.map(to_selected_model);
let commit_message = settings
.commit_message_model
.as_ref()
.filter(|s| is_provider_authenticated(s, registry_ref, cx))
.map(to_selected_model);
let thread_summary = settings
.thread_summary_model
.as_ref()
.filter(|s| is_provider_authenticated(s, registry_ref, cx))
.map(to_selected_model);
let inline_alternatives = settings
.inline_alternatives
.iter()
.filter(|s| is_provider_authenticated(s, registry_ref, cx))
.map(to_selected_model)
.collect::<Vec<_>>();

LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.update(cx, |registry, cx| {
registry.select_default_model(default.as_ref(), cx);
registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
registry.select_commit_message_model(commit_message.as_ref(), cx);
Expand Down
Loading
Loading