feat(geminicli): 添加内置 Gemini CLI OAuth 客户端常量和改进配置逻辑
- 添加 GeminiCLIOAuthClientID/Secret 常量(Gemini CLI 公开 OAuth 客户端) - 更新 DefaultAIStudioScopes 使用 generative-language.retriever(符合 Google 文档) - EffectiveOAuthConfig 支持自动回退到内置客户端 - 内置客户端自动过滤受限 scope(如 generative-language) - 添加 scope 向后兼容性处理
This commit is contained in:
@@ -22,11 +22,19 @@ const (
|
|||||||
// DefaultScopes for AI Studio (uses generativelanguage API with OAuth)
|
// DefaultScopes for AI Studio (uses generativelanguage API with OAuth)
|
||||||
// Reference: https://ai.google.dev/gemini-api/docs/oauth
|
// Reference: https://ai.google.dev/gemini-api/docs/oauth
|
||||||
// For regular Google accounts, supports API calls to generativelanguage.googleapis.com
|
// For regular Google accounts, supports API calls to generativelanguage.googleapis.com
|
||||||
DefaultAIStudioScopes = "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/generative-language"
|
// Note: Google Auth platform currently documents the OAuth scope as
|
||||||
|
// https://www.googleapis.com/auth/generative-language.retriever (often with cloud-platform).
|
||||||
|
DefaultAIStudioScopes = "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/generative-language.retriever"
|
||||||
|
|
||||||
// GeminiCLIRedirectURI is the redirect URI used by Gemini CLI for Code Assist OAuth.
|
// GeminiCLIRedirectURI is the redirect URI used by Gemini CLI for Code Assist OAuth.
|
||||||
GeminiCLIRedirectURI = "https://codeassist.google.com/authcode"
|
GeminiCLIRedirectURI = "https://codeassist.google.com/authcode"
|
||||||
|
|
||||||
|
// GeminiCLIOAuthClientID/Secret are the public OAuth client credentials used by Google Gemini CLI.
|
||||||
|
// They enable the "login without creating your own OAuth client" experience, but Google may
|
||||||
|
// restrict which scopes are allowed for this client.
|
||||||
|
GeminiCLIOAuthClientID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com"
|
||||||
|
GeminiCLIOAuthClientSecret = "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"
|
||||||
|
|
||||||
SessionTTL = 30 * time.Minute
|
SessionTTL = 30 * time.Minute
|
||||||
|
|
||||||
// GeminiCLIUserAgent mimics Gemini CLI to maximize compatibility with internal endpoints.
|
// GeminiCLIUserAgent mimics Gemini CLI to maximize compatibility with internal endpoints.
|
||||||
|
|||||||
@@ -140,9 +140,13 @@ func base64URLEncode(data []byte) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EffectiveOAuthConfig returns the effective OAuth configuration.
|
// EffectiveOAuthConfig returns the effective OAuth configuration.
|
||||||
// oauthType: "code_assist" or "ai_studio" (defaults to "code_assist" if empty)
|
// oauthType: "code_assist" or "ai_studio" (defaults to "code_assist" if empty).
|
||||||
// Returns error if ClientID or ClientSecret is not configured.
|
//
|
||||||
// Configure via GEMINI_OAUTH_CLIENT_ID and GEMINI_OAUTH_CLIENT_SECRET environment variables.
|
// If ClientID/ClientSecret is not provided, this falls back to the built-in Gemini CLI OAuth client.
|
||||||
|
//
|
||||||
|
// Note: The built-in Gemini CLI OAuth client is restricted and may reject some scopes (e.g.
|
||||||
|
// https://www.googleapis.com/auth/generative-language), which will surface as
|
||||||
|
// "restricted_client" / "Unregistered scope(s)" errors during browser authorization.
|
||||||
func EffectiveOAuthConfig(cfg OAuthConfig, oauthType string) (OAuthConfig, error) {
|
func EffectiveOAuthConfig(cfg OAuthConfig, oauthType string) (OAuthConfig, error) {
|
||||||
effective := OAuthConfig{
|
effective := OAuthConfig{
|
||||||
ClientID: strings.TrimSpace(cfg.ClientID),
|
ClientID: strings.TrimSpace(cfg.ClientID),
|
||||||
@@ -150,19 +154,61 @@ func EffectiveOAuthConfig(cfg OAuthConfig, oauthType string) (OAuthConfig, error
|
|||||||
Scopes: strings.TrimSpace(cfg.Scopes),
|
Scopes: strings.TrimSpace(cfg.Scopes),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require OAuth credentials to be configured
|
// Normalize scopes: allow comma-separated input but send space-delimited scopes to Google.
|
||||||
if effective.ClientID == "" || effective.ClientSecret == "" {
|
if effective.Scopes != "" {
|
||||||
return OAuthConfig{}, fmt.Errorf("gemini OAuth credentials not configured, set GEMINI_OAUTH_CLIENT_ID and GEMINI_OAUTH_CLIENT_SECRET environment variables")
|
effective.Scopes = strings.Join(strings.Fields(strings.ReplaceAll(effective.Scopes, ",", " ")), " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fall back to built-in Gemini CLI OAuth client when not configured.
|
||||||
|
if effective.ClientID == "" && effective.ClientSecret == "" {
|
||||||
|
effective.ClientID = GeminiCLIOAuthClientID
|
||||||
|
effective.ClientSecret = GeminiCLIOAuthClientSecret
|
||||||
|
} else if effective.ClientID == "" || effective.ClientSecret == "" {
|
||||||
|
return OAuthConfig{}, fmt.Errorf("OAuth client not configured: please set both client_id and client_secret (or leave both empty to use the built-in Gemini CLI client)")
|
||||||
|
}
|
||||||
|
|
||||||
|
isBuiltinClient := effective.ClientID == GeminiCLIOAuthClientID &&
|
||||||
|
effective.ClientSecret == GeminiCLIOAuthClientSecret
|
||||||
|
|
||||||
if effective.Scopes == "" {
|
if effective.Scopes == "" {
|
||||||
// Use different default scopes based on OAuth type
|
// Use different default scopes based on OAuth type
|
||||||
if oauthType == "ai_studio" {
|
if oauthType == "ai_studio" {
|
||||||
effective.Scopes = DefaultAIStudioScopes
|
// Built-in client can't request some AI Studio scopes (notably generative-language).
|
||||||
|
if isBuiltinClient {
|
||||||
|
effective.Scopes = DefaultCodeAssistScopes
|
||||||
|
} else {
|
||||||
|
effective.Scopes = DefaultAIStudioScopes
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Default to Code Assist scopes
|
// Default to Code Assist scopes
|
||||||
effective.Scopes = DefaultCodeAssistScopes
|
effective.Scopes = DefaultCodeAssistScopes
|
||||||
}
|
}
|
||||||
|
} else if oauthType == "ai_studio" && isBuiltinClient {
|
||||||
|
// If user overrides scopes while still using the built-in client, strip restricted scopes.
|
||||||
|
parts := strings.Fields(effective.Scopes)
|
||||||
|
filtered := make([]string, 0, len(parts))
|
||||||
|
for _, s := range parts {
|
||||||
|
if strings.Contains(s, "generative-language") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered = append(filtered, s)
|
||||||
|
}
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
effective.Scopes = DefaultCodeAssistScopes
|
||||||
|
} else {
|
||||||
|
effective.Scopes = strings.Join(filtered, " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility: normalize older AI Studio scope to the currently documented one.
|
||||||
|
if oauthType == "ai_studio" && effective.Scopes != "" {
|
||||||
|
parts := strings.Fields(effective.Scopes)
|
||||||
|
for i := range parts {
|
||||||
|
if parts[i] == "https://www.googleapis.com/auth/generative-language" {
|
||||||
|
parts[i] = "https://www.googleapis.com/auth/generative-language.retriever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
effective.Scopes = strings.Join(parts, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
return effective, nil
|
return effective, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user