mirror of
https://github.com/block/goose.git
synced 2026-04-28 03:29:36 +00:00
Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
parent
2977512bb5
commit
11f46dc0e0
9 changed files with 147 additions and 7 deletions
84
Cargo.lock
generated
84
Cargo.lock
generated
|
|
@ -202,6 +202,26 @@ dependencies = [
|
|||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"image 0.25.10",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.60.2",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.9.0"
|
||||
|
|
@ -4199,6 +4219,16 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||
dependencies = [
|
||||
"rustix 1.1.4",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.24"
|
||||
|
|
@ -4301,6 +4331,7 @@ dependencies = [
|
|||
"agent-client-protocol-schema",
|
||||
"ahash",
|
||||
"anyhow",
|
||||
"arboard",
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"aws-config",
|
||||
|
|
@ -6432,6 +6463,18 @@ dependencies = [
|
|||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"objc2",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.2"
|
||||
|
|
@ -6443,6 +6486,19 @@ dependencies = [
|
|||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-graphics"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
|
|
@ -6462,6 +6518,17 @@ dependencies = [
|
|||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.3.2"
|
||||
|
|
@ -12367,6 +12434,23 @@ dependencies = [
|
|||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix 1.1.4",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "x509-cert"
|
||||
version = "0.2.5"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ string_slice = "warn"
|
|||
rmcp = { version = "1.2.0", features = ["schemars", "auth"] }
|
||||
agent-client-protocol-schema = { version = "0.11", features = ["unstable"] }
|
||||
sacp = "11.0.0"
|
||||
arboard = "3"
|
||||
anyhow = "1.0"
|
||||
async-stream = "0.3"
|
||||
async-trait = "0.1"
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ rmcp = { workspace = true, features = [
|
|||
"transport-streamable-http-client-reqwest",
|
||||
] }
|
||||
oauth2 = { version = "5.0", default-features = false }
|
||||
arboard = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -266,9 +266,13 @@ pub struct ConfigKey {
|
|||
pub secret: bool,
|
||||
/// Optional default value for the key
|
||||
pub default: Option<String>,
|
||||
/// Whether this key should be configured using OAuth device code flow
|
||||
/// Whether this key should be configured using an OAuth flow
|
||||
/// When true, the provider's configure_oauth() method will be called instead of prompting for manual input
|
||||
pub oauth_flow: bool,
|
||||
/// Whether this OAuth flow uses the device code grant (RFC 8628)
|
||||
/// When true, the user must enter a verification code in the browser
|
||||
#[serde(default)]
|
||||
pub device_code_flow: bool,
|
||||
/// Whether this key should be shown prominently during provider setup
|
||||
/// (onboarding, settings modal, CLI configure)
|
||||
#[serde(default)]
|
||||
|
|
@ -290,6 +294,7 @@ impl ConfigKey {
|
|||
secret,
|
||||
default: default.map(|s| s.to_string()),
|
||||
oauth_flow: false,
|
||||
device_code_flow: false,
|
||||
primary,
|
||||
}
|
||||
}
|
||||
|
|
@ -301,11 +306,12 @@ impl ConfigKey {
|
|||
secret,
|
||||
default: Some(T::DEFAULT.to_string()),
|
||||
oauth_flow: false,
|
||||
device_code_flow: false,
|
||||
primary,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new ConfigKey that uses OAuth device code flow for configuration
|
||||
/// Create a new ConfigKey that uses an OAuth 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.
|
||||
|
|
@ -322,6 +328,29 @@ impl ConfigKey {
|
|||
secret,
|
||||
default: default.map(|s| s.to_string()),
|
||||
oauth_flow: true,
|
||||
device_code_flow: false,
|
||||
primary,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new ConfigKey that uses OAuth device code flow (RFC 8628) for configuration
|
||||
///
|
||||
/// Similar to new_oauth, but indicates the provider uses the device code grant where the user
|
||||
/// must enter a verification code in the browser.
|
||||
pub fn new_oauth_device_code(
|
||||
name: &str,
|
||||
required: bool,
|
||||
secret: bool,
|
||||
default: Option<&str>,
|
||||
primary: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
required,
|
||||
secret,
|
||||
default: default.map(|s| s.to_string()),
|
||||
oauth_flow: true,
|
||||
device_code_flow: true,
|
||||
primary,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,6 +285,16 @@ impl GithubCopilotProvider {
|
|||
async fn login(&self) -> Result<String> {
|
||||
let device_code_info = self.get_device_code().await?;
|
||||
|
||||
if let Ok(mut clipboard) = arboard::Clipboard::new() {
|
||||
if let Err(e) = clipboard.set_text(&device_code_info.user_code) {
|
||||
tracing::warn!("Failed to copy verification code to clipboard: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = webbrowser::open(&device_code_info.verification_uri) {
|
||||
tracing::warn!("Failed to open browser: {}", e);
|
||||
}
|
||||
|
||||
println!(
|
||||
"Please visit {} and enter code {}",
|
||||
device_code_info.verification_uri, device_code_info.user_code
|
||||
|
|
@ -402,7 +412,7 @@ impl ProviderDef for GithubCopilotProvider {
|
|||
GITHUB_COPILOT_DEFAULT_MODEL,
|
||||
GITHUB_COPILOT_KNOWN_MODELS.to_vec(),
|
||||
GITHUB_COPILOT_DOC_URL,
|
||||
vec![ConfigKey::new_oauth(
|
||||
vec![ConfigKey::new_oauth_device_code(
|
||||
"GITHUB_COPILOT_TOKEN",
|
||||
true,
|
||||
true,
|
||||
|
|
|
|||
|
|
@ -4157,13 +4157,17 @@
|
|||
"description": "Optional default value for the key",
|
||||
"nullable": true
|
||||
},
|
||||
"device_code_flow": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this OAuth flow uses the device code grant (RFC 8628)\nWhen true, the user must enter a verification code in the browser"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the configuration key (e.g., \"API_KEY\")"
|
||||
},
|
||||
"oauth_flow": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this key should be configured using OAuth device code flow\nWhen true, the provider's configure_oauth() method will be called instead of prompting for manual input"
|
||||
"description": "Whether this key should be configured using an OAuth flow\nWhen true, the provider's configure_oauth() method will be called instead of prompting for manual input"
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean",
|
||||
|
|
|
|||
|
|
@ -90,12 +90,17 @@ export type ConfigKey = {
|
|||
* Optional default value for the key
|
||||
*/
|
||||
default?: string | null;
|
||||
/**
|
||||
* Whether this OAuth flow uses the device code grant (RFC 8628)
|
||||
* When true, the user must enter a verification code in the browser
|
||||
*/
|
||||
device_code_flow?: boolean;
|
||||
/**
|
||||
* The name of the configuration key (e.g., "API_KEY")
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Whether this key should be configured using OAuth device code flow
|
||||
* Whether this key should be configured using an OAuth flow
|
||||
* When true, the provider's configure_oauth() method will be called instead of prompting for manual input
|
||||
*/
|
||||
oauth_flow: boolean;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ function OAuthForm({
|
|||
}
|
||||
};
|
||||
|
||||
const isDeviceCodeFlow = provider.metadata.config_keys.some((key) => key.device_code_flow);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-3 py-4">
|
||||
<Button
|
||||
|
|
@ -68,7 +70,9 @@ function OAuthForm({
|
|||
{isLoading ? 'Signing in...' : `Sign in with ${provider.metadata.display_name}`}
|
||||
</Button>
|
||||
<p className="text-xs text-text-muted text-center">
|
||||
A browser window will open for you to complete the login.
|
||||
{isDeviceCodeFlow
|
||||
? 'A browser window will open and the verification code will be copied to your clipboard. Paste it in the browser to complete sign-in.'
|
||||
: 'A browser window will open for you to complete the login.'}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -281,7 +281,9 @@ export default function ProviderConfigurationModal({
|
|||
: `Sign in with ${provider.metadata.display_name}`}
|
||||
</Button>
|
||||
<p className="text-sm text-text-secondary text-center">
|
||||
A browser window will open for you to complete the login.
|
||||
{provider.metadata.config_keys.some((key) => key.device_code_flow)
|
||||
? 'A browser window will open and the verification code will be copied to your clipboard. Paste it in the browser to complete sign-in.'
|
||||
: 'A browser window will open for you to complete the login.'}
|
||||
</p>
|
||||
</div>
|
||||
) : provider.metadata.config_keys.length === 0 &&
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue