acp: Detect gemini auth errors and show a button (#36641)

Closes #ISSUE

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin 2025-08-21 08:44:04 -06:00 committed by GitHub
parent d8fc779a67
commit d9ea97ee9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -76,11 +76,12 @@ enum ThreadError {
PaymentRequired,
ModelRequestLimitReached(cloud_llm_client::Plan),
ToolUseLimitReached,
AuthenticationRequired(SharedString),
Other(SharedString),
}
impl ThreadError {
fn from_err(error: anyhow::Error) -> Self {
fn from_err(error: anyhow::Error, agent: &Rc<dyn AgentServer>) -> Self {
if error.is::<language_model::PaymentRequiredError>() {
Self::PaymentRequired
} else if error.is::<language_model::ToolUseLimitReachedError>() {
@ -90,7 +91,17 @@ impl ThreadError {
{
Self::ModelRequestLimitReached(error.plan)
} else {
Self::Other(error.to_string().into())
let string = error.to_string();
// TODO: we should have Gemini return better errors here.
if agent.clone().downcast::<agent_servers::Gemini>().is_some()
&& string.contains("Could not load the default credentials")
|| string.contains("API key not valid")
|| string.contains("Request had invalid authentication credentials")
{
Self::AuthenticationRequired(string.into())
} else {
Self::Other(error.to_string().into())
}
}
}
}
@ -930,7 +941,7 @@ impl AcpThreadView {
}
fn handle_thread_error(&mut self, error: anyhow::Error, cx: &mut Context<Self>) {
self.thread_error = Some(ThreadError::from_err(error));
self.thread_error = Some(ThreadError::from_err(error, &self.agent));
cx.notify();
}
@ -4310,6 +4321,9 @@ impl AcpThreadView {
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
let content = match self.thread_error.as_ref()? {
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
ThreadError::AuthenticationRequired(error) => {
self.render_authentication_required_error(error.clone(), cx)
}
ThreadError::PaymentRequired => self.render_payment_required_error(cx),
ThreadError::ModelRequestLimitReached(plan) => {
self.render_model_request_limit_reached_error(*plan, cx)
@ -4348,6 +4362,24 @@ impl AcpThreadView {
.dismiss_action(self.dismiss_error_button(cx))
}
fn render_authentication_required_error(
&self,
error: SharedString,
cx: &mut Context<Self>,
) -> Callout {
Callout::new()
.severity(Severity::Error)
.title("Authentication Required")
.description(error.clone())
.actions_slot(
h_flex()
.gap_0p5()
.child(self.authenticate_button(cx))
.child(self.create_copy_button(error)),
)
.dismiss_action(self.dismiss_error_button(cx))
}
fn render_model_request_limit_reached_error(
&self,
plan: cloud_llm_client::Plan,
@ -4469,6 +4501,31 @@ impl AcpThreadView {
}))
}
fn authenticate_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
Button::new("authenticate", "Authenticate")
.label_size(LabelSize::Small)
.style(ButtonStyle::Filled)
.on_click(cx.listener({
move |this, _, window, cx| {
let agent = this.agent.clone();
let ThreadState::Ready { thread, .. } = &this.thread_state else {
return;
};
let connection = thread.read(cx).connection().clone();
let err = AuthRequired {
description: None,
provider_id: None,
};
this.clear_thread_error(cx);
let this = cx.weak_entity();
window.defer(cx, |window, cx| {
Self::handle_auth_required(this, err, agent, connection, window, cx);
})
}
}))
}
fn upgrade_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
Button::new("upgrade", "Upgrade")
.label_size(LabelSize::Small)