Skip to main content

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}