feat: add OAuth provider abstraction for CLI configuration (#3157)
Signed-off-by: Adam Tarantino <tarantino.adam@gmail.com>
This commit is contained in:
@@ -163,21 +163,45 @@ impl ProviderMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration key metadata for provider setup
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ConfigKey {
|
||||
/// The name of the configuration key (e.g., "API_KEY")
|
||||
pub name: String,
|
||||
/// Whether this key is required for the provider to function
|
||||
pub required: bool,
|
||||
/// Whether this key should be stored securely (e.g., in keychain)
|
||||
pub secret: bool,
|
||||
/// Optional default value for the key
|
||||
pub default: Option<String>,
|
||||
/// Whether this key should be configured using OAuth device code flow
|
||||
/// When true, the provider's configure_oauth() method will be called instead of prompting for manual input
|
||||
pub oauth_flow: bool,
|
||||
}
|
||||
|
||||
impl ConfigKey {
|
||||
/// Create a new ConfigKey
|
||||
pub fn new(name: &str, required: bool, secret: bool, default: Option<&str>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
required,
|
||||
secret,
|
||||
default: default.map(|s| s.to_string()),
|
||||
oauth_flow: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new ConfigKey that uses OAuth device code flow for configuration
|
||||
///
|
||||
/// This is used for providers that support OAuth authentication instead of manual API key entry.
|
||||
/// When oauth_flow is true, the configuration system will call the provider's configure_oauth() method.
|
||||
pub fn new_oauth(name: &str, required: bool, secret: bool, default: Option<&str>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
required,
|
||||
secret,
|
||||
default: default.map(|s| s.to_string()),
|
||||
oauth_flow: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,6 +412,23 @@ pub trait Provider: Send + Sync {
|
||||
}
|
||||
prompt
|
||||
}
|
||||
|
||||
/// Configure OAuth authentication for this provider
|
||||
///
|
||||
/// This method is called when a provider has configuration keys marked with oauth_flow = true.
|
||||
/// Providers that support OAuth should override this method to implement their specific OAuth flow.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(())` if OAuth configuration succeeds and credentials are saved
|
||||
/// * `Err(ProviderError)` if OAuth fails or is not supported by this provider
|
||||
///
|
||||
/// # Default Implementation
|
||||
/// The default implementation returns an error indicating OAuth is not supported.
|
||||
async fn configure_oauth(&self) -> Result<(), ProviderError> {
|
||||
Err(ProviderError::ExecutionError(
|
||||
"OAuth configuration not supported by this provider".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A message stream yields partial text content but complete tool calls, all within the Message object
|
||||
|
||||
@@ -387,7 +387,12 @@ impl Provider for GithubCopilotProvider {
|
||||
GITHUB_COPILOT_DEFAULT_MODEL,
|
||||
GITHUB_COPILOT_KNOWN_MODELS.to_vec(),
|
||||
GITHUB_COPILOT_DOC_URL,
|
||||
vec![ConfigKey::new("GITHUB_COPILOT_TOKEN", true, true, None)],
|
||||
vec![ConfigKey::new_oauth(
|
||||
"GITHUB_COPILOT_TOKEN",
|
||||
true,
|
||||
true,
|
||||
None,
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -466,4 +471,33 @@ impl Provider for GithubCopilotProvider {
|
||||
models.sort();
|
||||
Ok(Some(models))
|
||||
}
|
||||
|
||||
async fn configure_oauth(&self) -> Result<(), ProviderError> {
|
||||
let config = Config::global();
|
||||
|
||||
// Check if token already exists and is valid
|
||||
if config.get_secret::<String>("GITHUB_COPILOT_TOKEN").is_ok() {
|
||||
// Try to refresh API info to validate the token
|
||||
match self.refresh_api_info().await {
|
||||
Ok(_) => return Ok(()), // Token is valid
|
||||
Err(_) => {
|
||||
// Token is invalid, continue with OAuth flow
|
||||
tracing::debug!("Existing token is invalid, starting OAuth flow");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start OAuth device code flow
|
||||
let token = self
|
||||
.get_access_token()
|
||||
.await
|
||||
.map_err(|e| ProviderError::Authentication(format!("OAuth flow failed: {}", e)))?;
|
||||
|
||||
// Save the token
|
||||
config
|
||||
.set_secret("GITHUB_COPILOT_TOKEN", Value::String(token))
|
||||
.map_err(|e| ProviderError::ExecutionError(format!("Failed to save token: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user