| 1 | //! The auth seam for products with a sign-in of their own. |
| 2 | //! |
| 3 | //! Without a provider the agent only knows terminal auth: a client runs `<agent> --setup` |
| 4 | //! and the key lands in the `env` file. A product whose credentials come from somewhere else |
| 5 | //! (a browser sign-in, a stored session) supplies an [`AuthProvider`] from |
| 6 | //! [`Profile::auth`](crate::Profile::auth) and the agent defers to it. |
| 7 | |
| 8 | use agent_client_protocol::schema::v1::{AuthMethod, ClientCapabilities, RequestId}; |
| 9 | use agent_client_protocol::{Client, ConnectionTo}; |
| 10 | use async_trait::async_trait; |
| 11 | |
| 12 | use crate::llm::LlmConfig; |
| 13 | |
| 14 | /// What `authenticate` may use to reach the user. |
| 15 | pub struct AuthCtx<'a> { |
| 16 | /// The client connection, for a URL-mode elicitation or a notification. |
| 17 | pub connection: &'a ConnectionTo<Client>, |
| 18 | /// The `authenticate` request's own id. No session exists yet, so it scopes an |
| 19 | /// elicitation sent while the request is open. |
| 20 | pub request_id: RequestId, |
| 21 | /// What the client advertised in `initialize`, e.g. `elicitation.url`. |
| 22 | pub client_caps: &'a ClientCapabilities, |
| 23 | } |
| 24 | |
| 25 | impl AuthCtx<'_> { |
| 26 | /// Whether the client can show a URL-mode elicitation. |
| 27 | pub fn client_offers_url_elicitation(&self) -> bool { |
| 28 | self.client_caps |
| 29 | .elicitation |
| 30 | .as_ref() |
| 31 | .is_some_and(|caps| caps.url.is_some()) |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | #[async_trait] |
| 36 | pub trait AuthProvider: Send + Sync { |
| 37 | /// The method advertised in `initialize`, whatever the client's terminal capability, |
| 38 | /// e.g. `AuthMethod::Agent(AuthMethodAgent::new("sigit", "Sign in to siGit Code"))`. |
| 39 | fn method(&self) -> AuthMethod; |
| 40 | |
| 41 | /// The stored session as an endpoint, a bearer and a model, or `None` when signed out. |
| 42 | /// Read before each session request, so a sign-in finished in this process or another |
| 43 | /// is picked up without a restart. |
| 44 | fn llm_config(&self) -> Option<LlmConfig>; |
| 45 | |
| 46 | /// Run the sign-in. An existing session passes. An `Err` is shown to the user as is. |
| 47 | async fn authenticate(&self, ctx: AuthCtx<'_>) -> agent_client_protocol::Result<()>; |
| 48 | |
| 49 | /// Forget the stored session. |
| 50 | async fn logout(&self) -> anyhow::Result<()>; |
| 51 | } |