1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//! Picks up a new login after the provider refused a session's credentials.
//!
//! Providers capture their key or OAuth tokens when built, so a prompt that
//! fails with `auth_required` leaves the session holding the refused ones.
//! The next prompt rebuilds the provider from the credential store, so a
//! host's sign-in (or a `rho --login` elsewhere) applies without reloading.
use std::sync::Arc;
use super::{model_scoped_config, SessionHost};
use crate::{
app::acp::auth::CredentialFailure,
config::Config,
credential_store::{build_provider_from_config_ensuring_catalog, AppCredentialStore},
};
/// Whether the session's provider still holds credentials it was built with.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum SessionCredentials {
Current,
/// A prompt failed on a credential failure; reacquire before the next.
Stale,
}
impl SessionHost {
/// Rebuilds a [`SessionCredentials::Stale`] provider for the same model
/// and auth. Still-missing credentials fail with the typed provider error,
/// which the agent maps to `auth_required` again.
pub(super) async fn renew_stale_credentials(
&mut self,
process_config: &Config,
) -> anyhow::Result<()> {
if self.credentials == SessionCredentials::Current {
return Ok(());
}
let mut config = model_scoped_config(&self.current_model(), process_config);
config.reasoning = self.built.session.reasoning_level();
// The login that fixes it ran in another process, such as the
// editor's terminal login.
AppCredentialStore::reread_external_logins();
let provider =
build_provider_from_config_ensuring_catalog(&config, Arc::new(AppCredentialStore))
.await?;
// The model switch path also rebinds the compactor, which holds its
// own provider. The model is unchanged, so no switch notice is saved.
self.switch_provider(provider, &config)
.map_err(|error| match error.data {
Some(serde_json::Value::String(detail)) => {
anyhow::anyhow!("could not renew credentials: {detail}")
}
_ => anyhow::anyhow!("could not renew credentials: {}", error.message),
})?;
Ok(())
}
/// Marks the provider stale when `result` failed on its credentials.
pub(super) fn note_credential_failure<T>(&mut self, result: &anyhow::Result<T>) {
if result
.as_ref()
.is_err_and(|error| CredentialFailure::find(error).is_some())
{
self.credentials = SessionCredentials::Stale;
}
}
}