Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding oauth for gemini #1007

Closed
wants to merge 5 commits into from
Closed

feat: adding oauth for gemini #1007

wants to merge 5 commits into from

Conversation

damienrj
Copy link
Collaborator

This enables use of Gemini with either an API token, and oauth by setting GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

Comments suppressed due to low confidence (1)

crates/goose/src/providers/oauth.rs:345

  • The DEFAULT_REDIRECT_URL is hardcoded in the get_oauth_token_with_endpoints_async function. It should be passed as a parameter.
DEFAULT_REDIRECT_URL.to_string(),
@damienrj damienrj force-pushed the damien/gemini branch 2 times, most recently from 196fafc to cb6f005 Compare February 1, 2025 07:25
@damienrj damienrj marked this pull request as ready for review February 1, 2025 07:41
@damienrj damienrj requested a review from Copilot February 1, 2025 08:16
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

crates/goose/src/providers/oauth.rs:325

  • [nitpick] The get_oauth_token_with_endpoints_async function duplicates logic from get_oauth_token_async. Refactor to avoid code duplication.
pub async fn get_oauth_token_with_endpoints_async(

crates/goose/src/providers/oauth.rs Outdated Show resolved Hide resolved
@salman1993
Copy link
Collaborator

salman1993 commented Feb 2, 2025

the CLI is working for me but the behaviour is surprising. i did not see the OAuth consent screen. i did double check that GOOGLE_API_KEY wasn't set in env var or the keyring so i am not sure how it was still working. is that expected?

Screenshot 2025-02-02 at 11 19 50 AM

the frontend currently requires the API Key though:

@damienrj
Copy link
Collaborator Author

damienrj commented Feb 2, 2025

the CLI is working for me but the behaviour is surprising. i did not see the OAuth consent screen. i did double check that GOOGLE_API_KEY wasn't set in env var or the keyring so i am not sure how it was still working. is that expected?

Screenshot 2025-02-02 at 11 19 50 AM

the frontend currently requires the API Key though:

That is a good point about the GUI. I definitely was getting the oauth screen with Googe with the credential I made. Then are then cached in '~/Library/Application Support/Goose/google/oauth' so maybe it is there if you authed earlier. I had some debugging strings in earlier that showed it was pulling the Auth. Worth putting back?

@salman1993
Copy link
Collaborator

that'd be helpful. the oauth directory doesn't exist for me but very possible its cached somewhere else

❯ ll '~/Library/Application Support/Goose/google/oauth'
"~/Library/Application Support/Goose/google/oauth": No such file or directory (os error 2)

@damienrj
Copy link
Collaborator Author

damienrj commented Feb 3, 2025

that'd be helpful. the oauth directory doesn't exist for me but very possible its cached somewhere else

❯ ll '~/Library/Application Support/Goose/google/oauth'
"~/Library/Application Support/Goose/google/oauth": No such file or directory (os error 2)

Improved the error message

Failed to create provider: Authentication configuration missing. Please set either GOOGLE_API_KEY or both GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET

And in the generated log file it gives the path, and how long it will be valid.

  2025-02-03T07:05:03.636240Z  INFO goose::providers::oauth: Using cached OAuth token from /Users/damien/Library/Application Support/goose/google/oauth/aa6bd2d7e39124e3cae812c8e772dc8369e1581e0a737ab514660b7971084299.json valid until 2025-02-03 07:41:23.463162 UTC

This leaves goose configure to prompt for an API key. No sure on the best path for the configure and front end since there are now two options for one provider.

@@ -136,6 +241,8 @@ impl Provider for GoogleProvider {
vec![
ConfigKey::new("GOOGLE_API_KEY", true, true, None),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would the user be able to select oauth as an authentication method if the api key is required first?

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently via environment variable, some design choices need to be made for how configure/UI work if a Provider has more than one option. So while that is determined left configure and UI to only use the API token.

Copy link
Collaborator

@wendytang wendytang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How have you been testing this?

I tried

  • forcing the oauth configuration and providing valid client id, client secret values -> was able to use gemini in a session
  • forcing the oauth configuration and providing invalid client id, client secret values -> was able to use gemini in a session
  • using the api key and providing invalid api key value -> was able to use gemini in a session
image

so it seems a little strange to me that gemini is usable despite providing invalid credentials

Copy link
Collaborator

@wendytang wendytang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not mean to approve


async fn ensure_auth_header(&self) -> Result<String, ProviderError> {
match &self.auth {
GoogleAuth::ApiKey(key) => Ok(format!("Bearer {}", key)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to format this into a Bearer {} type token if it is only used in the url params key={api_key} ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can give that a try, went through a few iterations before I got it working.

crates/goose/src/providers/google.rs Outdated Show resolved Hide resolved

fs::create_dir_all(get_base_path()).unwrap();
let cache_path = get_base_path().join(format!("{}.json", hash));
let base_path = dirs::config_dir()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to do some consolidation around our config dir setup (which is out of scope of this PR), but we actually use ~/.config even on macos
dirs::config_dir() would drop us in $HOME/Library/Application Support

we should stick with the hard coded ~/.config` directory within this pr

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, can put it in ~/.config

let cache_path = get_base_path().join(format!("{}.json", hash));
let base_path = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("goose/google/oauth");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would drop databricks oauth tokens in to the google directory, we'll need to change that to either be generic or pass in some other named parameter to distinguish where any specific oauth implementation will go

let flow = OAuthFlow::new(
endpoints,
client_id.to_string(),
client_id.to_string(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would break any flows using fn get_oauth_token_async?, which i believe breaks the databricks provider. it would just pass in client_id twice, one as the client_secret even if not needed.

also for any callers of this, if it's a public client it wouldn't need the client_secret

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure. I will see about splitting this out.

@@ -60,6 +60,7 @@ serde_yaml = "0.9.34"
once_cell = "1.20.2"
dirs = "6.0.0"
rand = "0.8.5"
oauth2 = { version = "4.4", features = ["reqwest"] }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not super familiar with this library, we use https://crates.io/crates/yup-oauth2 in the goose-mcp crate, do you know what distinctions there are?

some larger questions around the oauth flow:

  1. whats the expected duration of the token?
  2. if short-lived will the changes automatically re-attempt an oauth flow if tokens are expired?
  3. does/should this oauth2 crate and implementation handle refresh tokens?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can see if it will also work with yup-oauth2, if that is already an existing library.

As to the flow, the token lasts an hour I believe. If the token is expired it will get a new token. Does the databrick token also expire?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks to be about the same for databricks, about an hour and then we do the same oauth local browser flow

Yeah we use yup-oauth2 but in a slightly different context, for google's Desktop client OAuth flow, but hopefully it does work

@damienrj
Copy link
Collaborator Author

damienrj commented Feb 3, 2025

How have you been testing this?

I tried

  • forcing the oauth configuration and providing valid client id, client secret values -> was able to use gemini in a session
  • forcing the oauth configuration and providing invalid client id, client secret values -> was able to use gemini in a session
  • using the api key and providing invalid api key value -> was able to use gemini in a session
image so it seems a little strange to me that gemini is usable despite providing invalid credentials

Have been testing with different sets of environment variables GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRECT=..., or GOOGLE_API_KEY...

How have you been testing this?

I tried

  • forcing the oauth configuration and providing valid client id, client secret values -> was able to use gemini in a session
  • forcing the oauth configuration and providing invalid client id, client secret values -> was able to use gemini in a session
  • using the api key and providing invalid api key value -> was able to use gemini in a session
image so it seems a little strange to me that gemini is usable despite providing invalid credentials

Maybe the fallback logic for the cached oauth credential needs updating since I think it isn't trying to use the new client_Id, and client_secret if the cached credentials are present. Maybe configure needs to delete the existing credential.

Co-authored-by: Kalvin C <kalvinnchau@users.noreply.github.com>
@damienrj damienrj closed this Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants