use crate::error::ProviderError;
use base64::prelude::*;
use gcloud_auth::credentials::CredentialsFile;
use gcloud_auth::{project::Config, token::DefaultTokenSourceProvider};
use std::env;
use tracing::{debug, instrument};
const SCOPES: [&str; 2] = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/generative-language",
];
#[derive(Debug)]
pub struct GoogleCredentials {
pub project_id: String,
pub location: String,
pub token_provider: DefaultTokenSourceProvider,
}
impl GoogleCredentials {
pub fn from_token_provider(
token_provider: DefaultTokenSourceProvider,
) -> Result<Self, ProviderError> {
let project_id = match &token_provider.project_id {
Some(id) => id.clone(),
None => match env::var("GOOGLE_CLOUD_PROJECT") {
Ok(val) => val,
Err(_) => Err(ProviderError::NoProjectIdFound)?,
},
};
let location =
env::var("GOOGLE_CLOUD_LOCATION").unwrap_or_else(|_| "us-central1".to_string());
Ok(GoogleCredentials {
project_id,
location,
token_provider,
})
}
pub async fn get_access_token(&self) -> Result<String, ProviderError> {
let token = self
.token_provider
.token_source()
.token()
.await
.map_err(|e| ProviderError::TokenError(e.to_string()))?;
Ok(token)
}
}
#[allow(unused_imports)]
use token_source::TokenSourceProvider;
pub async fn create_token_provider() -> Result<GoogleCredentials, ProviderError> {
let creds = CredentialBuilder::new().await?.creds;
let config = Config::default().with_scopes(&SCOPES);
let provider =
DefaultTokenSourceProvider::new_with_credentials(config, Box::new(creds)).await?;
GoogleCredentials::from_token_provider(provider)
}
pub struct CredentialBuilder {
pub creds: CredentialsFile,
}
impl CredentialBuilder {
pub async fn new() -> Result<Self, ProviderError> {
let creds = Self::build().await?;
let creds = CredentialBuilder { creds };
Ok(creds)
}
#[instrument(skip_all)]
async fn build() -> Result<CredentialsFile, ProviderError> {
if let Ok(base64_creds) = env::var("GOOGLE_ACCOUNT_JSON_BASE64") {
debug!("Using GOOGLE_ACCOUNT_JSON_BASE64 for credentials");
let decoded_creds = Self::decode_base64_str(&base64_creds)?;
return Ok(CredentialsFile::new_from_str(&decoded_creds).await?);
}
debug!("Using GOOGLE_APPLICATION_CREDENTIALS for credentials",);
return CredentialsFile::new()
.await
.map_err(ProviderError::GCloudAuthError);
}
fn decode_base64_str(service_base64_creds: &str) -> Result<String, ProviderError> {
let decoded = BASE64_STANDARD.decode(service_base64_creds)?;
Ok(String::from_utf8(decoded)?)
}
}
#[derive(Debug)]
pub enum GoogleAuth {
ApiKey(String),
GoogleCredentials(GoogleCredentials),
NotSet,
}
impl GoogleAuth {
#[instrument(skip_all)]
pub async fn from_env() -> Self {
if let Ok(api_key) = std::env::var("GEMINI_API_KEY").or(std::env::var("GOOGLE_API_KEY")) {
if !api_key.is_empty() {
debug!("Using GEMINI_API_KEY for authentication");
return Self::ApiKey(api_key);
}
}
match create_token_provider().await {
Ok(credentials) => {
debug!("Using Google Application Credentials for authentication");
Self::GoogleCredentials(credentials)
}
Err(e) => {
debug!("Failed to create Google token provider: {}", e);
Self::NotSet
}
}
}
}
enum GoogleApiVersion {
V1Beta,
V1Beta1, V1,
}
impl GoogleApiVersion {
fn from_env() -> Option<Self> {
match std::env::var("GOOGLE_API_VERSION") {
Ok(ver) => match ver.to_lowercase().as_str() {
"v1beta" => Some(GoogleApiVersion::V1Beta),
"v1beta1" => Some(GoogleApiVersion::V1Beta1),
"v1" => Some(GoogleApiVersion::V1),
_ => None,
},
Err(_) => None,
}
}
fn as_str(&self) -> &'static str {
match self {
GoogleApiVersion::V1Beta => "v1beta",
GoogleApiVersion::V1 => "v1",
GoogleApiVersion::V1Beta1 => "v1beta1",
}
}
}
pub enum GoogleUrl {
Gemini,
Vertex,
}
impl GoogleUrl {
pub fn base_url(&self, auth: &GoogleAuth) -> String {
match self {
GoogleUrl::Gemini => {
let version = GoogleApiVersion::from_env().unwrap_or(GoogleApiVersion::V1Beta);
format!(
"https://generativelanguage.googleapis.com/{}/models",
version.as_str()
)
}
GoogleUrl::Vertex => match auth {
GoogleAuth::GoogleCredentials(creds) => {
let version = GoogleApiVersion::from_env().unwrap_or(GoogleApiVersion::V1Beta1);
format!(
"https://{}-aiplatform.googleapis.com/{}/projects/{}/locations/{}/publishers/google/models",
creds.location,
version.as_str(),
creds.project_id,
creds.location
)
}
_ => "https://generativelanguage.googleapis.com/v1beta/models".to_string(),
},
}
}
}