rig_core/client/copilot.rs
1//! The Copilot client: a [`CopilotConfig`] on a transport, and the models it
2//! builds.
3
4use crate::client::macros::http_client;
5use crate::driver::Model;
6use crate::error::ProviderError;
7use crate::model::ModelList;
8
9use crate::providers::copilot::auth::{AuthError, Authenticator};
10use crate::providers::copilot::wire::{CopilotConfig, CopilotWire, Embeddings};
11
12http_client!(
13 /// GitHub Copilot: its [`CopilotConfig`] on a transport. Every model it
14 /// builds sends through that transport.
15 Copilot,
16 CopilotConfig
17);
18
19impl Copilot {
20 /// Copilot with an exchanged session token, on the shared reqwest
21 /// client. See [`CopilotConfig::new`] for how the endpoint is chosen.
22 #[cfg(feature = "reqwest")]
23 #[cfg_attr(docsrs, doc(cfg(feature = "reqwest")))]
24 pub fn new(api_key: impl Into<crate::wire::Secret>) -> Self {
25 CopilotConfig::new(api_key).client()
26 }
27
28 /// Copilot from the environment ([`CopilotConfig::from_env`]), on the
29 /// shared reqwest client. This does not exchange a token.
30 #[cfg(feature = "reqwest")]
31 #[cfg_attr(docsrs, doc(cfg(feature = "reqwest")))]
32 pub fn from_env() -> Result<Self, crate::client::env::EnvError> {
33 Ok(CopilotConfig::from_env()?.client())
34 }
35
36 /// The completion model for `model`, on whichever route answers it.
37 pub fn completion(&self, model: impl Into<String>) -> Model<CopilotWire> {
38 self.model(self.config.completion(model))
39 }
40
41 /// The embedding model for `model`, `ndims` wide when set.
42 pub fn embedding(&self, model: impl Into<String>, ndims: Option<usize>) -> Model<Embeddings> {
43 self.model(self.config.embedding(model, ndims))
44 }
45
46 /// The models this session can use.
47 pub async fn list_models(&self) -> Result<ModelList, ProviderError> {
48 self.model(self.config.models()).list().await
49 }
50
51 /// A client on this client's transport, configured with the session
52 /// `authenticator` resolves: the device login, the GitHub token
53 /// exchange and the refresh all send through this client's transport.
54 /// The session token replaces the credential; the API root is the one
55 /// the exchange names, unless this client's was set explicitly.
56 ///
57 /// ```no_run
58 /// use rig_core::providers::copilot::{CopilotConfig, auth::{AuthSource, Authenticator, DeviceCodeHandler}};
59 ///
60 /// # async fn run(http: rig_core::http_client::DynHttpClient) -> Result<(), Box<dyn std::error::Error>> {
61 /// let authenticator = Authenticator::new(AuthSource::OAuth, None, None, DeviceCodeHandler::default(), true);
62 /// let copilot = CopilotConfig::new("").connect(http).authenticate(&authenticator).await?;
63 /// # let _ = copilot;
64 /// # Ok(())
65 /// # }
66 /// ```
67 pub async fn authenticate(self, authenticator: &Authenticator) -> Result<Self, AuthError> {
68 let context = authenticator.auth_context(&self.http).await?;
69 let explicit = self.config.base_url != CopilotConfig::new(self.config.api_key).base_url;
70 let signed_in = CopilotConfig::from_auth(&context);
71 let config = if explicit {
72 signed_in.with_base_url(self.config.base_url)
73 } else {
74 signed_in
75 };
76 Ok(Self {
77 config,
78 http: self.http,
79 })
80 }
81}