Skip to main content

CopilotAuthHandler

Struct CopilotAuthHandler 

Source
pub struct CopilotAuthHandler { /* private fields */ }
Expand description

Handler for GitHub Copilot authentication.

Manages the complete authentication lifecycle including:

  • Device code flow for initial authentication
  • Token caching and validation
  • Automatic token refresh
  • Silent authentication attempts

§Architecture

The handler implements a hierarchical token resolution strategy:

  1. Cached Copilot Token: Check local cache for valid token
  2. Environment Variable: Check COPILOT_API_KEY
  3. Cached Access Token: Use cached GitHub token to fetch new Copilot token
  4. Interactive Flow: Prompt user via device code flow

§Thread Safety

The handler is thread-safe and can be cloned cheaply. Authentication operations are protected by a global lock to prevent concurrent flows.

§Example

use std::sync::Arc;
use reqwest_middleware::ClientWithMiddleware;
use bamboo_agent::agent::llm::providers::copilot::auth::handler::CopilotAuthHandler;

async fn example() -> anyhow::Result<()> {
    let client = Arc::new(ClientWithMiddleware::new(/* ... */));
    let handler = CopilotAuthHandler::new(
        client,
        std::path::PathBuf::from("~/.bamboo"),
        false,
    );

    // Will use cached token or trigger device flow
    let token = handler.get_token().await?;
    println!("Got token: {}", token);
    Ok(())
}

Implementations§

Source§

impl CopilotAuthHandler

Source

pub fn new( client: Arc<ClientWithMiddleware>, app_data_dir: PathBuf, headless_auth: bool, ) -> Self

Creates a new authentication handler.

§Arguments
  • client - HTTP client with middleware for retry logic and error handling
  • app_data_dir - Directory for storing cached tokens (.token and .copilot_token.json)
  • headless_auth - Whether to print authentication instructions to console. Set to true for CLI applications, false for GUI applications.
§Example
use std::sync::Arc;
use reqwest_middleware::ClientWithMiddleware;
use bamboo_agent::agent::llm::providers::copilot::auth::handler::CopilotAuthHandler;

let client = Arc::new(ClientWithMiddleware::new(/* ... */));
let handler = CopilotAuthHandler::new(
    client,
    std::path::PathBuf::from("~/.bamboo"),
    true, // CLI mode
);
Source

pub fn app_data_dir(&self) -> &PathBuf

Returns the application data directory path.

This directory contains cached tokens:

  • .token: GitHub OAuth access token
  • .copilot_token.json: Copilot API configuration
Source

pub async fn authenticate(&self) -> Result<String>

Performs authentication and returns an access token.

This is the primary entry point for authentication. It will attempt silent authentication first, then fall back to interactive device flow if necessary.

§Returns

A Copilot API token on success.

§Errors

Returns an error if:

  • All authentication methods fail
  • User denies authorization during device flow
  • Device code expires before authorization
  • Network errors occur
Source

pub async fn ensure_authenticated(&self) -> Result<()>

Ensures the handler is authenticated, without returning the token.

This is useful for pre-authenticating or verifying credentials without needing the actual token value.

§Example
// Pre-authenticate before starting the application
handler.ensure_authenticated().await?;
println!("Authentication successful!");
Source

pub async fn get_token(&self) -> Result<String>

Gets the current access token, authenticating if necessary.

Alias for authenticate.

Source

pub async fn get_chat_token(&self) -> Result<String>

Gets a chat token, using cached credentials or triggering device flow.

This method attempts silent authentication first, then falls back to interactive device code flow if necessary.

§Silent Authentication Priority
  1. Check cached Copilot token (.copilot_token.json)
  2. Check COPILOT_API_KEY environment variable
  3. Check cached GitHub access token (.token) and exchange for new Copilot token
§Thread Safety

This method acquires a global lock to prevent concurrent authentication flows. Only one authentication attempt can be in progress at a time.

§Returns

A valid Copilot API token.

§Errors

Returns an error if all authentication methods fail.

Source

pub async fn start_authentication(&self) -> Result<DeviceCodeResponse>

Starts the authentication process by getting a device code.

This method initiates the OAuth device flow by requesting a device code from GitHub. If headless_auth is false, it prints user-friendly instructions to the console.

§Display Behavior
  • Headless mode (headless_auth = true): Prints full instructions with ASCII art
  • GUI mode (headless_auth = false): Returns device code for custom UI
§Returns

A DeviceCodeResponse with the device code and verification information.

§Example
let device_code = handler.start_authentication().await?;
// In GUI mode, display these values to the user
println!("URL: {}", device_code.verification_uri);
println!("Code: {}", device_code.user_code);
Source

pub async fn complete_authentication( &self, device_code: &DeviceCodeResponse, ) -> Result<CopilotConfig>

Completes authentication by polling for access token and exchanging for Copilot token.

This method completes the OAuth flow by:

  1. Polling GitHub for the access token (waits for user authorization)
  2. Exchanging the access token for a Copilot API token
  3. Caching both tokens to disk for future use
§Arguments
§Returns

A CopilotConfig containing the Copilot API token and configuration.

§Side Effects

Writes the following files to app_data_dir:

  • .token: GitHub OAuth access token
  • .copilot_token.json: Copilot API configuration
§Errors

Returns an error if:

  • User denies authorization
  • Device code expires before authorization
  • Token exchange fails
  • File writing fails
Source

pub async fn try_get_chat_token_silent(&self) -> Result<Option<String>>

Attempts silent authentication without user interaction.

This method tries multiple authentication strategies in order of preference, all of which can succeed without requiring user interaction:

  1. Cached Copilot Token: Load from .copilot_token.json if still valid
  2. Environment Variable: Check COPILOT_API_KEY
  3. Cached Access Token: Use cached GitHub token to fetch new Copilot token
§Returns
  • Ok(Some(token)) if silent authentication succeeded
  • Ok(None) if silent authentication is not possible (triggers interactive flow)
  • Err(...) if an unexpected error occurred
§Side Effects

If using a cached access token, this method will:

  • Fetch a new Copilot token from GitHub
  • Cache the new Copilot token to .copilot_token.json
  • Remove the cached access token if it’s invalid
§Example
match handler.try_get_chat_token_silent().await? {
    Some(token) => println!("Got token silently: {}", token),
    None => println!("Need interactive authentication"),
}
Source

pub async fn force_refresh_chat_token(&self) -> Result<Option<String>>

Force refresh a Copilot token using the cached GitHub OAuth access token.

This bypasses the .copilot_token.json cache and is useful when the cached Copilot token is rejected early (e.g. revoked) even if it hasn’t reached expires_at yet.

Returns:

  • Ok(Some(token)) if the refresh succeeded
  • Ok(None) if no cached access token exists

Trait Implementations§

Source§

impl Clone for CopilotAuthHandler

Source§

fn clone(&self) -> CopilotAuthHandler

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for CopilotAuthHandler

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more