use std::sync::{Arc, Mutex};
use super::manager::Auth;
use crate::auth::{AuthError, Credential, CredentialProvider, CredentialSource};
use crate::errors::{AuthOperation, Lm15Error};
use crate::router::DeclaredProvider;
use crate::transport::BoxFuture;
pub struct ManagedRoute {
pub credential: Option<Arc<dyn CredentialProvider + Send + Sync>>,
pub named: Option<String>,
pub base_url: Option<String>,
pub account_id: Option<String>,
}
pub fn managed_route(
auth: &Auth,
provider: &str,
placeholder: Option<String>,
hosted: bool,
) -> Result<ManagedRoute, Lm15Error> {
match auth.selection(provider) {
Ok((connection, shape)) => {
if let Some(named) = shape.named {
return Ok(ManagedRoute {
credential: None,
named: Some(named),
base_url: None,
account_id: None,
});
}
let origin = format!(
"managed connection {} ({})",
connection.id, connection.label
);
Ok(ManagedRoute {
credential: Some(Arc::new(ManagedCredential {
auth: auth.clone(),
provider: provider.to_string(),
origin,
current: Mutex::new(None),
})),
named: None,
base_url: shape.base_url,
account_id: shape.account_id,
})
}
Err(error) if error.reason() == Some("login_required") => {
if let Some(key) = placeholder {
if !auth.status(provider).map(|s| s.logged_out).unwrap_or(false) {
return Ok(ManagedRoute {
credential: Some(Arc::new(key)),
named: None,
base_url: None,
account_id: None,
});
}
}
if hosted {
let mut error = AuthOperation::error(
format!("{provider}: no saved connection in this scope; the machine's cloud identity is not used under a managed Auth — save a named identity (Auth::configure with the cloud method) or pass RouterConfig::credential explicitly"),
"login_required", "resolution", "not_committed", "select_connection",
);
error.meta_mut().provider = Some(provider.to_string());
return Err(error);
}
Err(error)
}
Err(error) => Err(error),
}
}
struct ManagedCredential {
auth: Auth,
provider: String,
origin: String,
current: Mutex<Option<Credential>>,
}
impl CredentialProvider for ManagedCredential {
fn credential(&self) -> Result<Credential, AuthError> {
self.current.lock().expect("managed credential").clone().ok_or_else(|| AuthError::NotConfigured {
provider: Some(self.provider.clone()),
message: "a managed connection's credential is resolved before each request; send through the adapter".into(),
hint: None,
})
}
fn source(&self) -> Option<CredentialSource> {
Some(CredentialSource {
kind: "connection".into(),
label: self.origin.clone(),
named: None,
expires_at: None,
})
}
fn prepare(&self) -> Option<BoxFuture<'_, Result<(), AuthError>>> {
Some(Box::pin(async move {
let auth = self
.auth
.request_auth(&self.provider, None)
.await
.map_err(|e| AuthError::Lm15(Box::new(e)))?;
let credential = match auth.credential {
Some(("bearer", value)) => Credential::BearerToken {
value,
expires_at: None,
},
Some((_, value)) => Credential::ApiKey { value },
None => {
return Err(AuthError::Lm15(Box::new(AuthOperation::error(
format!(
"{}: this connection names a cloud identity, not a credential",
self.provider
),
"method_unavailable",
"resolution",
"not_committed",
"operator_action",
))))
}
};
*self.current.lock().expect("managed credential") = Some(credential);
Ok(())
}))
}
}
pub fn declared_providers() -> Vec<DeclaredProvider> {
use crate::generated::tables;
let base_url = |policy: &crate::auth::AccessPolicy| {
policy
.base_url
.expect("a declared-login policy names its base URL in the table")
};
vec![
DeclaredProvider::anthropic(
"kimi-code",
base_url(&crate::auth::KIMI_CODE),
tables::KIMI_CODE_COMPAT,
)
.factory(|builder| builder.access_policy(&crate::auth::KIMI_CODE).build()),
DeclaredProvider::chat(
"github-copilot",
base_url(&crate::auth::GITHUB_COPILOT),
tables::GITHUB_COPILOT_COMPAT,
)
.factory(|builder| builder.access_policy(&crate::auth::GITHUB_COPILOT).build()),
]
}