Skip to content

Commit 11f46dc

Browse files
jh-blockDouwe Osinga
andauthored
fix: GitHub Copilot auth fails to open browser in Desktop app (#6957) (#8019)
Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
1 parent 2977512 commit 11f46dc

9 files changed

Lines changed: 147 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 84 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ string_slice = "warn"
2222
rmcp = { version = "1.2.0", features = ["schemars", "auth"] }
2323
agent-client-protocol-schema = { version = "0.11", features = ["unstable"] }
2424
sacp = "11.0.0"
25+
arboard = "3"
2526
anyhow = "1.0"
2627
async-stream = "0.3"
2728
async-trait = "0.1"

crates/goose/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ rmcp = { workspace = true, features = [
6969
"transport-streamable-http-client-reqwest",
7070
] }
7171
oauth2 = { version = "5.0", default-features = false }
72+
arboard = { workspace = true }
7273
anyhow = { workspace = true }
7374
thiserror = { workspace = true }
7475
futures = { workspace = true }

crates/goose/src/providers/base.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,13 @@ pub struct ConfigKey {
266266
pub secret: bool,
267267
/// Optional default value for the key
268268
pub default: Option<String>,
269-
/// Whether this key should be configured using OAuth device code flow
269+
/// Whether this key should be configured using an OAuth flow
270270
/// When true, the provider's configure_oauth() method will be called instead of prompting for manual input
271271
pub oauth_flow: bool,
272+
/// Whether this OAuth flow uses the device code grant (RFC 8628)
273+
/// When true, the user must enter a verification code in the browser
274+
#[serde(default)]
275+
pub device_code_flow: bool,
272276
/// Whether this key should be shown prominently during provider setup
273277
/// (onboarding, settings modal, CLI configure)
274278
#[serde(default)]
@@ -290,6 +294,7 @@ impl ConfigKey {
290294
secret,
291295
default: default.map(|s| s.to_string()),
292296
oauth_flow: false,
297+
device_code_flow: false,
293298
primary,
294299
}
295300
}
@@ -301,11 +306,12 @@ impl ConfigKey {
301306
secret,
302307
default: Some(T::DEFAULT.to_string()),
303308
oauth_flow: false,
309+
device_code_flow: false,
304310
primary,
305311
}
306312
}
307313

308-
/// Create a new ConfigKey that uses OAuth device code flow for configuration
314+
/// Create a new ConfigKey that uses an OAuth flow for configuration
309315
///
310316
/// This is used for providers that support OAuth authentication instead of manual API key entry.
311317
/// When oauth_flow is true, the configuration system will call the provider's configure_oauth() method.
@@ -322,6 +328,29 @@ impl ConfigKey {
322328
secret,
323329
default: default.map(|s| s.to_string()),
324330
oauth_flow: true,
331+
device_code_flow: false,
332+
primary,
333+
}
334+
}
335+
336+
/// Create a new ConfigKey that uses OAuth device code flow (RFC 8628) for configuration
337+
///
338+
/// Similar to new_oauth, but indicates the provider uses the device code grant where the user
339+
/// must enter a verification code in the browser.
340+
pub fn new_oauth_device_code(
341+
name: &str,
342+
required: bool,
343+
secret: bool,
344+
default: Option<&str>,
345+
primary: bool,
346+
) -> Self {
347+
Self {
348+
name: name.to_string(),
349+
required,
350+
secret,
351+
default: default.map(|s| s.to_string()),
352+
oauth_flow: true,
353+
device_code_flow: true,
325354
primary,
326355
}
327356
}

crates/goose/src/providers/githubcopilot.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,16 @@ impl GithubCopilotProvider {
285285
async fn login(&self) -> Result<String> {
286286
let device_code_info = self.get_device_code().await?;
287287

288+
if let Ok(mut clipboard) = arboard::Clipboard::new() {
289+
if let Err(e) = clipboard.set_text(&device_code_info.user_code) {
290+
tracing::warn!("Failed to copy verification code to clipboard: {}", e);
291+
}
292+
}
293+
294+
if let Err(e) = webbrowser::open(&device_code_info.verification_uri) {
295+
tracing::warn!("Failed to open browser: {}", e);
296+
}
297+
288298
println!(
289299
"Please visit {} and enter code {}",
290300
device_code_info.verification_uri, device_code_info.user_code
@@ -402,7 +412,7 @@ impl ProviderDef for GithubCopilotProvider {
402412
GITHUB_COPILOT_DEFAULT_MODEL,
403413
GITHUB_COPILOT_KNOWN_MODELS.to_vec(),
404414
GITHUB_COPILOT_DOC_URL,
405-
vec![ConfigKey::new_oauth(
415+
vec![ConfigKey::new_oauth_device_code(
406416
"GITHUB_COPILOT_TOKEN",
407417
true,
408418
true,

ui/desktop/openapi.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4157,13 +4157,17 @@
41574157
"description": "Optional default value for the key",
41584158
"nullable": true
41594159
},
4160+
"device_code_flow": {
4161+
"type": "boolean",
4162+
"description": "Whether this OAuth flow uses the device code grant (RFC 8628)\nWhen true, the user must enter a verification code in the browser"
4163+
},
41604164
"name": {
41614165
"type": "string",
41624166
"description": "The name of the configuration key (e.g., \"API_KEY\")"
41634167
},
41644168
"oauth_flow": {
41654169
"type": "boolean",
4166-
"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"
4170+
"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"
41674171
},
41684172
"primary": {
41694173
"type": "boolean",

ui/desktop/src/api/types.gen.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,17 @@ export type ConfigKey = {
9090
* Optional default value for the key
9191
*/
9292
default?: string | null;
93+
/**
94+
* Whether this OAuth flow uses the device code grant (RFC 8628)
95+
* When true, the user must enter a verification code in the browser
96+
*/
97+
device_code_flow?: boolean;
9398
/**
9499
* The name of the configuration key (e.g., "API_KEY")
95100
*/
96101
name: string;
97102
/**
98-
* Whether this key should be configured using OAuth device code flow
103+
* Whether this key should be configured using an OAuth flow
99104
* When true, the provider's configure_oauth() method will be called instead of prompting for manual input
100105
*/
101106
oauth_flow: boolean;

ui/desktop/src/components/onboarding/ProviderConfigForm.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ function OAuthForm({
5656
}
5757
};
5858

59+
const isDeviceCodeFlow = provider.metadata.config_keys.some((key) => key.device_code_flow);
60+
5961
return (
6062
<div className="flex flex-col items-center gap-3 py-4">
6163
<Button
@@ -68,7 +70,9 @@ function OAuthForm({
6870
{isLoading ? 'Signing in...' : `Sign in with ${provider.metadata.display_name}`}
6971
</Button>
7072
<p className="text-xs text-text-muted text-center">
71-
A browser window will open for you to complete the login.
73+
{isDeviceCodeFlow
74+
? 'A browser window will open and the verification code will be copied to your clipboard. Paste it in the browser to complete sign-in.'
75+
: 'A browser window will open for you to complete the login.'}
7276
</p>
7377
</div>
7478
);

ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,9 @@ export default function ProviderConfigurationModal({
281281
: `Sign in with ${provider.metadata.display_name}`}
282282
</Button>
283283
<p className="text-sm text-text-secondary text-center">
284-
A browser window will open for you to complete the login.
284+
{provider.metadata.config_keys.some((key) => key.device_code_flow)
285+
? 'A browser window will open and the verification code will be copied to your clipboard. Paste it in the browser to complete sign-in.'
286+
: 'A browser window will open for you to complete the login.'}
285287
</p>
286288
</div>
287289
) : provider.metadata.config_keys.length === 0 &&

0 commit comments

Comments
 (0)