diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 122b143eb657..5943a5b44b8c 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -323,36 +323,23 @@ async fn handle_existing_config() -> anyhow::Result<()> { /// Helper function to handle OAuth configuration for a provider async fn handle_oauth_configuration(provider_name: &str, key_name: &str) -> anyhow::Result<()> { - let _ = cliclack::log::info(format!( - "Configuring {} using OAuth device code flow...", - key_name - )); - - // Create a temporary provider instance to handle OAuth let temp_model = ModelConfig::new("temp")?.with_canonical_limits(provider_name); - match create(provider_name, temp_model, Vec::new()).await { - Ok(provider) => match provider.configure_oauth().await { - Ok(_) => { - let _ = cliclack::log::success("OAuth authentication completed successfully!"); - Ok(()) - } - Err(e) => { - let _ = cliclack::log::error(format!("Failed to authenticate: {}", e)); - Err(anyhow::anyhow!( - "OAuth authentication failed for {}: {}", - key_name, - e - )) - } - }, - Err(e) => { + let provider = create(provider_name, temp_model, Vec::new()) + .await + .map_err(|e| { let _ = cliclack::log::error(format!("Failed to create provider for OAuth: {}", e)); - Err(anyhow::anyhow!( - "Failed to create provider for OAuth: {}", - e - )) - } - } + anyhow::anyhow!("Failed to create provider for OAuth: {}", e) + })?; + + let _ = cliclack::log::info(format!("Configuring {} using OAuth...", key_name)); + + provider.configure_oauth().await.map_err(|e| { + let _ = cliclack::log::error(format!("Failed to authenticate: {}", e)); + anyhow::anyhow!("OAuth authentication failed for {}: {}", key_name, e) + })?; + + let _ = cliclack::log::success("OAuth configuration completed"); + Ok(()) } fn interactive_model_search(models: &[String]) -> anyhow::Result { diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index d5770225e15b..4a0a526bcb8d 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -412,6 +412,7 @@ derive_utoipa!(Icon as IconSchema); super::routes::config_management::check_provider, super::routes::config_management::set_config_provider, super::routes::config_management::configure_provider_oauth, + super::routes::config_management::check_oauth_completion, super::routes::config_management::get_canonical_model_info, super::routes::prompts::get_prompts, super::routes::prompts::get_prompt, @@ -514,6 +515,10 @@ derive_utoipa!(Icon as IconSchema); super::routes::config_management::ModelInfoQuery, super::routes::config_management::ModelInfoResponse, super::routes::config_management::ModelInfoData, + super::routes::config_management::OauthCompletedResponse, + super::routes::config_management::DeviceCodeResponse, + super::routes::config_management::OauthResponse, + super::routes::config_management::OauthCompletionResponse, super::routes::prompts::PromptsListResponse, super::routes::prompts::PromptContentResponse, super::routes::prompts::SavePromptRequest, diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 60cdff93d9a0..36963626d94f 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -830,6 +830,88 @@ pub async fn get_provider_catalog_template( Ok(Json(template)) } +#[derive(Serialize, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct OauthCompletedResponse { + pub message: String, +} + +#[derive(Serialize, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct DeviceCodeResponse { + pub user_code: String, + pub verification_uri: String, +} + +#[derive(Serialize, ToSchema)] +#[serde(untagged)] +pub enum OauthResponse { + Completed(OauthCompletedResponse), + DeviceCode(DeviceCodeResponse), +} + +#[derive(Serialize, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct OauthCompletionResponse { + pub completed: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub debug: Option, +} + +#[utoipa::path( + get, + path = "/config/providers/{name}/oauth/completion", + params( + ("name" = String, Path, description = "Provider name") + ), + responses( + (status = 200, description = "OAuth completion status", body = OauthCompletionResponse), + (status = 400, description = "Failed to check OAuth completion") + ) +)] +pub async fn check_oauth_completion( + Path(provider_name): Path, +) -> Result, ErrorResponse> { + use goose::model::ModelConfig; + use goose::providers::create; + + if !is_valid_provider_name(&provider_name) { + return Err(ErrorResponse::bad_request(format!( + "Invalid provider name: '{}'", + provider_name + ))); + } + + let temp_model = ModelConfig::new("temp") + .map_err(|e| { + ErrorResponse::bad_request(format!("Failed to create temporary model config: {}", e)) + })? + .with_canonical_limits(&provider_name); + + let provider = create(&provider_name, temp_model, Vec::new()) + .await + .map_err(|e| { + ErrorResponse::bad_request(format!( + "Failed to create provider '{}': {}", + provider_name, e + )) + })?; + + let has_token = goose::config::Config::global() + .get_secret::("GITHUB_COPILOT_TOKEN") + .is_ok(); + + let (completed, debug_msg) = match provider.check_oauth_completion().await { + Ok(val) => (val, format!("ok({}) has_token_before={}", val, has_token)), + Err(e) => { + let msg = format!("err: {} has_token_before={}", e, has_token); + return Ok(Json(OauthCompletionResponse { completed: false, debug: Some(msg) })); + } + }; + + Ok(Json(OauthCompletionResponse { completed, debug: Some(debug_msg) })) +} + #[utoipa::path( post, path = "/config/providers/{name}/oauth", @@ -837,13 +919,13 @@ pub async fn get_provider_catalog_template( ("name" = String, Path, description = "Provider name") ), responses( - (status = 200, description = "OAuth configuration completed"), + (status = 200, description = "OAuth configuration completed or device code returned", body = OauthResponse), (status = 400, description = "OAuth configuration failed") ) )] pub async fn configure_provider_oauth( Path(provider_name): Path, -) -> Result, ErrorResponse> { +) -> Result, ErrorResponse> { use goose::model::ModelConfig; use goose::providers::create; @@ -860,7 +942,6 @@ pub async fn configure_provider_oauth( })? .with_canonical_limits(&provider_name); - // OAuth configuration does not use extensions. let provider = create(&provider_name, temp_model, Vec::new()) .await .map_err(|e| { @@ -870,6 +951,18 @@ pub async fn configure_provider_oauth( )) })?; + if let Some(device_code_data) = provider.get_oauth_device_code_info().await.map_err(|e| { + ErrorResponse::bad_request(format!( + "Failed to get OAuth device code for provider '{}': {}", + provider_name, e + )) + })? { + return Ok(Json(OauthResponse::DeviceCode(DeviceCodeResponse { + user_code: device_code_data.user_code, + verification_uri: device_code_data.verification_uri, + }))); + } + provider.configure_oauth().await.map_err(|e| { ErrorResponse::bad_request(format!( "OAuth configuration failed for provider '{}': {}", @@ -877,12 +970,13 @@ pub async fn configure_provider_oauth( )) })?; - // Mark the provider as configured after successful OAuth let configured_marker = format!("{}_configured", provider_name); let config = goose::config::Config::global(); config.set_param(&configured_marker, true)?; - Ok(Json("OAuth configuration completed".to_string())) + Ok(Json(OauthResponse::Completed(OauthCompletedResponse { + message: "OAuth configuration completed".to_string(), + }))) } pub fn routes(state: Arc) -> Router { @@ -896,6 +990,14 @@ pub fn routes(state: Arc) -> Router { .route("/config/extensions/{name}", delete(remove_extension)) .route("/config/providers", get(providers)) .route("/config/providers/{name}/models", get(get_provider_models)) + .route( + "/config/providers/{name}/oauth", + post(configure_provider_oauth), + ) + .route( + "/config/providers/{name}/oauth/completion", + get(check_oauth_completion), + ) .route("/config/provider-catalog", get(get_provider_catalog)) .route( "/config/provider-catalog/{id}", @@ -921,10 +1023,6 @@ pub fn routes(state: Arc) -> Router { .route("/config/custom-providers/{id}", get(get_custom_provider)) .route("/config/check_provider", post(check_provider)) .route("/config/set_provider", post(set_config_provider)) - .route( - "/config/providers/{name}/oauth", - post(configure_provider_oauth), - ) .with_state(state) } diff --git a/crates/goose/src/providers/base.rs b/crates/goose/src/providers/base.rs index b389a2bd6268..00871c80349d 100644 --- a/crates/goose/src/providers/base.rs +++ b/crates/goose/src/providers/base.rs @@ -451,6 +451,15 @@ pub trait LeadWorkerProviderTrait { fn get_settings(&self) -> (usize, usize, usize); } +/// OAuth flow completed successfully (callback-based providers) +/// Device code info for OAuth device code flow (UI only) +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct DeviceCodeData { + pub user_code: String, + pub verification_uri: String, +} + /// Base trait for AI providers (OpenAI, Anthropic, etc) #[async_trait] pub trait Provider: Send + Sync { @@ -687,6 +696,9 @@ pub trait Provider: Send + Sync { /// 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. /// + /// This method blocks until OAuth flow completes (callback flow for browser-based auth, + /// device code flow for terminal-based auth where code is displayed and completion is awaited). + /// /// # Returns /// * `Ok(())` if OAuth configuration succeeds and credentials are saved /// * `Err(ProviderError)` if OAuth fails or is not supported by this provider @@ -699,6 +711,30 @@ pub trait Provider: Send + Sync { )) } + /// Get device code info for OAuth device code flow (UI only) + /// + /// For providers that use device code flow (like GitHub Copilot), this returns the + /// device code and verification URI without completing the OAuth flow. This allows + /// the UI to display the code and let the user complete authorization in a browser. + /// + /// The OAuth flow is then completed by calling configure_oauth() after the user + /// has authorized on the verification page. + /// + /// # Returns + /// * `Ok(Some(DeviceCodeData))` if this provider uses device code flow + /// * `Ok(None)` if this provider doesn't use device code flow + /// * `Err(ProviderError)` if an error occurs + /// + /// # Default Implementation + /// Returns Ok(None) for providers that don't use device code flow. + async fn get_oauth_device_code_info(&self) -> Result, ProviderError> { + Ok(None) + } + + async fn check_oauth_completion(&self) -> Result { + Ok(false) + } + fn permission_routing(&self) -> PermissionRouting { PermissionRouting::Noop } @@ -1070,4 +1106,39 @@ mod tests { assert_eq!(info.output_token_cost, Some(0.00001)); assert_eq!(info.currency, Some("$".to_string())); } + + #[test] + fn test_device_code_data_serializes() { + let data = DeviceCodeData { + user_code: "ABCD-1234".to_string(), + verification_uri: "https://github.com/verify".to_string(), + }; + let json = serde_json::to_string(&data).unwrap(); + + assert!(json.contains("ABCD-1234")); + assert!(json.contains("verificationUri")); + assert!(json.contains("userCode")); + } + + #[test] + fn test_device_code_data_deserialize() { + let json = r#"{"userCode":"ABCD-EFGH","verificationUri":"https://github.com/verify"}"#; + let data: DeviceCodeData = serde_json::from_str(json).unwrap(); + + assert_eq!(data.user_code, "ABCD-EFGH"); + assert_eq!(data.verification_uri, "https://github.com/verify"); + } + + #[test] + fn test_device_code_data_roundtrip() { + let original = DeviceCodeData { + user_code: "WXYZ-5678".to_string(), + verification_uri: "https://example.com/auth".to_string(), + }; + let json = serde_json::to_string(&original).unwrap(); + let deserialized: DeviceCodeData = serde_json::from_str(&json).unwrap(); + + assert_eq!(original.user_code, deserialized.user_code); + assert_eq!(original.verification_uri, deserialized.verification_uri); + } } diff --git a/crates/goose/src/providers/githubcopilot.rs b/crates/goose/src/providers/githubcopilot.rs index e4fadb9d8f2f..ed27b68754d9 100644 --- a/crates/goose/src/providers/githubcopilot.rs +++ b/crates/goose/src/providers/githubcopilot.rs @@ -11,20 +11,24 @@ use serde_json::Value; use std::cell::RefCell; use std::collections::HashMap; use std::path::PathBuf; +use std::sync::LazyLock; use std::time::Duration; +use webbrowser; -use super::base::{Provider, ProviderDef, ProviderMetadata, ProviderUsage, Usage}; -use super::errors::ProviderError; use super::formats::openai::{create_request, get_usage, response_to_message}; use super::openai_compatible::handle_response_openai_compat; use super::retry::ProviderRetry; use super::utils::{get_model, ImageFormat, RequestLog}; +use crate::providers::base::{ + ConfigKey, DeviceCodeData, MessageStream, Provider, ProviderDef, ProviderMetadata, + ProviderUsage, Usage, +}; +use crate::providers::errors::ProviderError; use crate::config::{Config, ConfigError}; use crate::conversation::message::Message; use crate::model::ModelConfig; -use crate::providers::base::{ConfigKey, MessageStream}; use futures::future::BoxFuture; use rmcp::model::Tool; @@ -59,6 +63,9 @@ const GITHUB_COPILOT_DEVICE_CODE_URL: &str = "https://github.com/login/device/co const GITHUB_COPILOT_ACCESS_TOKEN_URL: &str = "https://github.com/login/oauth/access_token"; const GITHUB_COPILOT_API_KEY_URL: &str = "https://api.github.com/copilot_internal/v2/token"; +static OAUTH_DEVICE_CODE: LazyLock>> = + LazyLock::new(|| tokio::sync::Mutex::new(None)); + #[derive(Debug, Deserialize)] struct DeviceCodeInfo { device_code: String, @@ -66,6 +73,11 @@ struct DeviceCodeInfo { verification_uri: String, } +#[derive(Debug, Clone)] +struct StoredDeviceCode { + device_code: String, +} + #[derive(Debug, Serialize, Deserialize, Clone)] struct CopilotTokenEndpoints { api: String, @@ -262,9 +274,27 @@ impl GithubCopilotProvider { async fn get_access_token(&self) -> Result { for attempt in 0..3 { tracing::trace!("attempt {} to get access token", attempt + 1); - match self.login().await { - Ok(token) => return Ok(token), - Err(err) => tracing::warn!("failed to get access token: {}", err), + + let mut device_code_guard = OAUTH_DEVICE_CODE.lock().await; + + if let Some(stored) = device_code_guard.take() { + drop(device_code_guard); + match self.poll_for_access_token(&stored.device_code).await { + Ok(token) => return Ok(token), + Err(err) => { + tracing::warn!("failed to get access token: {}", err); + // Put the device code back so user can retry with same code + let mut guard = OAUTH_DEVICE_CODE.lock().await; + *guard = Some(stored); + drop(guard); + } + } + } else { + drop(device_code_guard); + match self.login().await { + Ok(token) => return Ok(token), + Err(err) => tracing::warn!("failed to get access token: {}", err), + } } } Err(anyhow!("failed to get access token after 3 attempts")) @@ -273,11 +303,24 @@ impl GithubCopilotProvider { async fn login(&self) -> Result { let device_code_info = self.get_device_code().await?; + let code = device_code_info.user_code.as_str(); + + tracing::info!("GitHub Copilot code: {}", code); + + webbrowser::open(&device_code_info.verification_uri).ok(); println!( "Please visit {} and enter code {}", - device_code_info.verification_uri, device_code_info.user_code + device_code_info.verification_uri, code ); + // Store the device code for polling + let stored = StoredDeviceCode { + device_code: device_code_info.device_code.clone(), + }; + let mut guard = OAUTH_DEVICE_CODE.lock().await; + *guard = Some(stored); + drop(guard); + self.poll_for_access_token(&device_code_info.device_code) .await } @@ -305,7 +348,7 @@ impl GithubCopilotProvider { .context("failed to parse device code response") } - async fn poll_for_access_token(&self, device_code: &str) -> Result { + pub async fn poll_for_access_token(&self, device_code: &str) -> Result { #[derive(Serialize)] struct AccessTokenRequest { client_id: String, @@ -362,6 +405,74 @@ impl GithubCopilotProvider { Err(anyhow!("failed to get access token")) } + async fn check_access_token_once(&self, device_code: &str) -> Result { + #[derive(Serialize)] + struct AccessTokenRequest { + client_id: String, + device_code: String, + grant_type: String, + } + #[derive(Debug, Deserialize)] + struct AccessTokenResponse { + access_token: Option, + error: Option, + #[serde(flatten)] + _extra: HashMap, + } + + let resp = self + .client + .post(GITHUB_COPILOT_ACCESS_TOKEN_URL) + .headers(self.get_github_headers()) + .json(&AccessTokenRequest { + client_id: GITHUB_COPILOT_CLIENT_ID.to_string(), + device_code: device_code.to_string(), + grant_type: "urn:ietf:params:oauth:grant-type:device_code".to_string(), + }) + .send() + .await + .context("failed to make request while checking access token")? + .error_for_status() + .context("error checking access token")? + .json::() + .await + .context("failed to parse response while checking access token")?; + + eprintln!("[DEBUG] check_access_token_once response: access_token={}, error={}", + resp.access_token.is_some(), + resp.error.as_deref().unwrap_or("none")); + + if let Some(access_token) = resp.access_token { + Ok(access_token) + } else if resp + .error + .as_ref() + .is_some_and(|err| err == "authorization_pending") + { + Err(anyhow!("authorization pending")) + } else if resp + .error + .as_ref() + .is_some_and(|err| err == "slow_down") + { + Err(anyhow!("authorization pending - slow down")) + } else if resp + .error + .as_ref() + .is_some_and(|err| err == "expired_token") + { + Err(anyhow!("device code expired")) + } else if resp + .error + .as_ref() + .is_some_and(|err| err == "access_denied") + { + Err(anyhow!("access denied")) + } else { + Err(anyhow!("unexpected response: {:#?}", resp)) + } + } + fn get_github_headers(&self) -> http::HeaderMap { let mut headers = http::HeaderMap::new(); headers.insert(http::header::ACCEPT, "application/json".parse().unwrap()); @@ -543,34 +654,102 @@ impl Provider for GithubCopilotProvider { async fn configure_oauth(&self) -> Result<(), ProviderError> { let config = Config::global(); - // Check if token already exists and is valid if config.get_secret::("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 + Ok(_) => return Ok(()), 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", &token) .map_err(|e| ProviderError::ExecutionError(format!("Failed to save token: {}", e)))?; Ok(()) } -} -// Copilot sometimes returns multiple choices in a completion response for + async fn get_oauth_device_code_info(&self) -> Result, ProviderError> { + let config = Config::global(); + + if config.get_secret::("GITHUB_COPILOT_TOKEN").is_ok() { + return Ok(None); + } + + // Always generate a fresh device code to avoid returning stale/used codes on retry + let device_code_info = self.get_device_code().await.map_err(|e| { + ProviderError::Authentication(format!("Failed to get device code: {}", e)) + })?; + + let stored = StoredDeviceCode { + device_code: device_code_info.device_code.clone(), + }; + + let mut guard = OAUTH_DEVICE_CODE.lock().await; + *guard = Some(stored); + drop(guard); + + Ok(Some(DeviceCodeData { + user_code: device_code_info.user_code, + verification_uri: device_code_info.verification_uri, + })) + } + + async fn check_oauth_completion(&self) -> Result { + let config = Config::global(); + + if config.get_secret::("GITHUB_COPILOT_TOKEN").is_ok() { + return Ok(true); + } + + let guard = OAUTH_DEVICE_CODE.lock().await; + let stored = guard.as_ref().map(|s| s.device_code.clone()); + drop(guard); + + if let Some(device_code) = stored { + match self.check_access_token_once(&device_code).await { + Ok(token) => { + const MAX_SAVE_ATTEMPTS: u32 = 3; + for attempt in 0..MAX_SAVE_ATTEMPTS { + match config.set_secret("GITHUB_COPILOT_TOKEN", &token) { + Ok(_) => { + let mut guard = OAUTH_DEVICE_CODE.lock().await; + *guard = None; + return Ok(true); + } + Err(e) => { + if attempt < MAX_SAVE_ATTEMPTS - 1 { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } else { + return Err(ProviderError::ExecutionError(format!( + "Failed to save token: {}", + e + ))); + } + } + } + } + } + Err(e) => { + if e.to_string().contains("expired") || e.to_string().contains("access_denied") { + let mut guard = OAUTH_DEVICE_CODE.lock().await; + *guard = None; + return Err(ProviderError::Authentication(e.to_string())); + } + return Ok(false); + } + } + } + + Ok(false) + } +} // Claude models and places the `tool_calls` payload in a non-zero index choice. // Example: // - Choice 0: {"finish_reason":"stop","message":{"content":"I'll check the Desktop directory…"}} diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 3f953ea395f8..3ebf4a138d36 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -1416,7 +1416,14 @@ ], "responses": { "200": { - "description": "OAuth configuration completed" + "description": "OAuth configuration completed or device code returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OauthResponse" + } + } + } }, "400": { "description": "OAuth configuration failed" @@ -1424,6 +1431,40 @@ } } }, + "/config/providers/{name}/oauth/completion": { + "get": { + "tags": [ + "super::routes::config_management" + ], + "operationId": "check_oauth_completion", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Provider name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OAuth completion status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OauthCompletionResponse" + } + } + } + }, + "400": { + "description": "Failed to check OAuth completion" + } + } + } + }, "/config/read": { "post": { "tags": [ @@ -4481,6 +4522,21 @@ } } }, + "DeviceCodeResponse": { + "type": "object", + "required": [ + "userCode", + "verificationUri" + ], + "properties": { + "userCode": { + "type": "string" + }, + "verificationUri": { + "type": "string" + } + } + }, "DictationProvider": { "type": "string", "enum": [ @@ -6354,6 +6410,42 @@ } } }, + "OauthCompletedResponse": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + }, + "OauthCompletionResponse": { + "type": "object", + "required": [ + "completed" + ], + "properties": { + "completed": { + "type": "boolean" + }, + "debug": { + "type": "string", + "nullable": true + } + } + }, + "OauthResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/OauthCompletedResponse" + }, + { + "$ref": "#/components/schemas/DeviceCodeResponse" + } + ] + }, "ParseRecipeRequest": { "type": "object", "required": [ diff --git a/ui/desktop/src/api/index.ts b/ui/desktop/src/api/index.ts index 2a521a90dc32..281464697fa3 100644 --- a/ui/desktop/src/api/index.ts +++ b/ui/desktop/src/api/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { addExtension, agentAddExtension, agentRemoveExtension, backupConfig, callTool, cancelDownload, cancelLocalModelDownload, checkProvider, configureProviderOauth, confirmToolAction, createCustomProvider, createRecipe, createSchedule, decodeRecipe, deleteLocalModel, deleteModel, deleteRecipe, deleteSchedule, deleteSession, detectProvider, diagnostics, downloadHfModel, downloadModel, encodeRecipe, exportApp, exportSession, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getLocalModelDownloadProgress, getModelSettings, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModels, getRepoFiles, getSession, getSessionExtensions, getSessionInsights, getSlashCommands, getTools, getTunnelStatus, importApp, importSession, initConfig, inspectRunningJob, killRunningJob, listApps, listLocalModels, listModels, listRecipes, listSchedules, listSessions, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, readResource, recipeToYaml, recoverConfig, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchHfModels, searchSessions, sendTelemetryEvent, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, startOpenrouterSetup, startTetrateSetup, startTunnel, status, stopAgent, stopTunnel, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateModelSettings, updateSchedule, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, upsertPermissions, validateConfig } from './sdk.gen'; -export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, BackupConfigData, BackupConfigErrors, BackupConfigResponse, BackupConfigResponses, CallToolData, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, ChatRequest, CheckProviderData, CheckProviderRequest, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderRequest, DetectProviderResponse, DetectProviderResponse2, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponse, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelRequest, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCanonicalModelInfoData, GetCanonicalModelInfoResponse, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponse, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponse, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponse, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponse, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponse, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, HfGgufFile, HfModelInfo, HfQuantVariant, Icon, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponse, InitConfigResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponse, ListLocalModelsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, LocalModelResponse, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelCapabilities, ModelConfig, ModelDownloadStatus, ModelInfo, ModelInfoData, ModelInfoQuery, ModelInfoResponse, ModelSettings, ModelTemplate, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderCatalogEntry, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, ReasoningContent, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponse, RecoverConfigResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, RepoVariantsResponse, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SamplingConfig, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponse, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponse, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionDisplayInfo, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponse, UpdateModelSettingsResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; +export { addExtension, agentAddExtension, agentRemoveExtension, backupConfig, callTool, cancelDownload, cancelLocalModelDownload, checkOauthCompletion, checkProvider, configureProviderOauth, confirmToolAction, createCustomProvider, createRecipe, createSchedule, decodeRecipe, deleteLocalModel, deleteModel, deleteRecipe, deleteSchedule, deleteSession, detectProvider, diagnostics, downloadHfModel, downloadModel, encodeRecipe, exportApp, exportSession, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getLocalModelDownloadProgress, getModelSettings, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModels, getRepoFiles, getSession, getSessionExtensions, getSessionInsights, getSlashCommands, getTools, getTunnelStatus, importApp, importSession, initConfig, inspectRunningJob, killRunningJob, listApps, listLocalModels, listModels, listRecipes, listSchedules, listSessions, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, readResource, recipeToYaml, recoverConfig, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchHfModels, searchSessions, sendTelemetryEvent, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, startOpenrouterSetup, startTetrateSetup, startTunnel, status, stopAgent, stopTunnel, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateModelSettings, updateSchedule, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, upsertPermissions, validateConfig } from './sdk.gen'; +export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, BackupConfigData, BackupConfigErrors, BackupConfigResponse, BackupConfigResponses, CallToolData, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, ChatRequest, CheckOauthCompletionData, CheckOauthCompletionErrors, CheckOauthCompletionResponse, CheckOauthCompletionResponses, CheckProviderData, CheckProviderRequest, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponse, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderRequest, DetectProviderResponse, DetectProviderResponse2, DetectProviderResponses, DeviceCodeResponse, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponse, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelRequest, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCanonicalModelInfoData, GetCanonicalModelInfoResponse, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponse, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponse, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponse, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponse, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponse, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, HfGgufFile, HfModelInfo, HfQuantVariant, Icon, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponse, InitConfigResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponse, ListLocalModelsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, LocalModelResponse, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelCapabilities, ModelConfig, ModelDownloadStatus, ModelInfo, ModelInfoData, ModelInfoQuery, ModelInfoResponse, ModelSettings, ModelTemplate, OauthCompletedResponse, OauthCompletionResponse, OauthResponse, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderCatalogEntry, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, ReasoningContent, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponse, RecoverConfigResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, RepoVariantsResponse, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SamplingConfig, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponse, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponse, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionDisplayInfo, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponse, UpdateModelSettingsResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index bf342dee7e0e..13f2e009b6fe 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CheckProviderData, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportAppData, ExportAppErrors, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, ForkSessionData, ForkSessionErrors, ForkSessionResponses, GetCanonicalModelInfoData, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponses, GetPromptsData, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportAppData, ImportAppErrors, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsErrors, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponses, ListModelsData, ListModelsResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SavePromptData, SavePromptErrors, SavePromptResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; +import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CheckOauthCompletionData, CheckOauthCompletionErrors, CheckOauthCompletionResponses, CheckProviderData, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportAppData, ExportAppErrors, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, ForkSessionData, ForkSessionErrors, ForkSessionResponses, GetCanonicalModelInfoData, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponses, GetPromptsData, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportAppData, ImportAppErrors, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsErrors, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponses, ListModelsData, ListModelsResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SavePromptData, SavePromptErrors, SavePromptResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; export type Options = Options2 & { /** @@ -243,6 +243,8 @@ export const getProviderModels = (options: export const configureProviderOauth = (options: Options) => (options.client ?? client).post({ url: '/config/providers/{name}/oauth', ...options }); +export const checkOauthCompletion = (options: Options) => (options.client ?? client).get({ url: '/config/providers/{name}/oauth/completion', ...options }); + export const readConfig = (options: Options) => (options.client ?? client).post({ url: '/config/read', ...options, diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 4fea314c474d..2a627c9fda54 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -234,6 +234,11 @@ export type DetectProviderResponse = { provider_name: string; }; +export type DeviceCodeResponse = { + userCode: string; + verificationUri: string; +}; + export type DictationProvider = 'openai' | 'elevenlabs' | 'groq' | 'local'; export type DictationProviderStatus = { @@ -822,6 +827,17 @@ export type ModelTemplate = { name: string; }; +export type OauthCompletedResponse = { + message: string; +}; + +export type OauthCompletionResponse = { + completed: boolean; + debug?: string | null; +}; + +export type OauthResponse = OauthCompletedResponse | DeviceCodeResponse; + export type ParseRecipeRequest = { content: string; }; @@ -2677,11 +2693,41 @@ export type ConfigureProviderOauthErrors = { export type ConfigureProviderOauthResponses = { /** - * OAuth configuration completed + * OAuth configuration completed or device code returned */ - 200: unknown; + 200: OauthResponse; +}; + +export type ConfigureProviderOauthResponse = ConfigureProviderOauthResponses[keyof ConfigureProviderOauthResponses]; + +export type CheckOauthCompletionData = { + body?: never; + path: { + /** + * Provider name + */ + name: string; + }; + query?: never; + url: '/config/providers/{name}/oauth/completion'; }; +export type CheckOauthCompletionErrors = { + /** + * Failed to check OAuth completion + */ + 400: unknown; +}; + +export type CheckOauthCompletionResponses = { + /** + * OAuth completion status + */ + 200: OauthCompletionResponse; +}; + +export type CheckOauthCompletionResponse = CheckOauthCompletionResponses[keyof CheckOauthCompletionResponses]; + export type ReadConfigData = { body: ConfigKeyQuery; path?: never; diff --git a/ui/desktop/src/components/DeviceCodeModal.tsx b/ui/desktop/src/components/DeviceCodeModal.tsx new file mode 100644 index 000000000000..17b4ef1e1065 --- /dev/null +++ b/ui/desktop/src/components/DeviceCodeModal.tsx @@ -0,0 +1,181 @@ +import { useState, useEffect, useRef } from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from './ui/dialog'; +import { Button } from './ui/button'; +import { Copy } from 'lucide-react'; +import type { DeviceCodeResponse } from '../api/types.gen'; +import { checkOauthCompletion } from '../api'; + +interface DeviceCodeModalProps { + isOpen: boolean; + deviceCodeData?: DeviceCodeResponse; + onAuthorized: () => void; + onCancel: () => void; + onRetry: () => void; +} + +const POLL_INTERVAL_MS = 5000; +const POLL_TIMEOUT_MS = 180000; + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export function DeviceCodeModal({ + isOpen, + deviceCodeData, + onAuthorized, + onCancel, + onRetry, +}: DeviceCodeModalProps) { + const [copied, setCopied] = useState(false); + const [isChecking, setIsChecking] = useState(false); + const [error, setError] = useState(null); + const browserOpenedRef = useRef(false); + const authorizedRef = useRef(false); + const onAuthorizedRef = useRef(onAuthorized); + const onRetryRef = useRef(onRetry); + onAuthorizedRef.current = onAuthorized; + onRetryRef.current = onRetry; + + useEffect(() => { + if (!isOpen || !deviceCodeData) { + browserOpenedRef.current = false; + authorizedRef.current = false; + return; + } + + if (!browserOpenedRef.current) { + window.open(deviceCodeData.verificationUri, '_blank'); + browserOpenedRef.current = true; + } + + let active = true; + authorizedRef.current = false; + setIsChecking(true); + setError(null); + + const poll = async () => { + const startTime = Date.now(); + + while (active) { + if (Date.now() - startTime > POLL_TIMEOUT_MS) { + if (active) { + setIsChecking(false); + setError('Authorization timed out. Please refresh the code or try again.'); + } + return; + } + + try { + const response = await checkOauthCompletion({ + path: { name: 'github_copilot' }, + }); + + console.log('[DeviceCodeModal] poll response:', JSON.stringify(response.data), 'error:', JSON.stringify((response as any).error)); + + if (response.data?.completed && active) { + authorizedRef.current = true; + setIsChecking(false); + setError(null); + onAuthorizedRef.current(); + return; + } + } catch (err) { + console.error('[DeviceCodeModal] poll exception:', err); + } + + if (active) { + await sleep(POLL_INTERVAL_MS); + } + } + }; + + poll(); + + return () => { + active = false; + }; + }, [isOpen, deviceCodeData]); + + const handleRetry = () => { + onRetryRef.current(); + }; + + const handleCopy = async () => { + if (deviceCodeData) { + await navigator.clipboard.writeText(deviceCodeData.userCode); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + return ( + !open && !authorizedRef.current && onCancel()}> + + + GitHub Copilot Setup + + Enter the code below on GitHub to authorize your account + + + +
+
+
+ +
+
+ + {deviceCodeData?.userCode} + + +
+
+ +

+ {error ? ( + {error} + ) : isChecking ? ( + 'Waiting for you to authorize on GitHub. Enter the code in the browser, and we will automatically complete setup.' + ) : ( + 'A browser window has been opened. Copy the code above and enter it on GitHub.' + )} +

+
+ + {error && ( + + + + )} +
+
+ ); +} diff --git a/ui/desktop/src/components/ProviderGuard.tsx b/ui/desktop/src/components/ProviderGuard.tsx index 99b3f5d0f4c5..3b18dd7a9804 100644 --- a/ui/desktop/src/components/ProviderGuard.tsx +++ b/ui/desktop/src/components/ProviderGuard.tsx @@ -2,9 +2,11 @@ import { useEffect, useState, useMemo, useRef, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { useConfig } from './ConfigContext'; import { SetupModal } from './SetupModal'; +import { DeviceCodeModal } from './DeviceCodeModal'; import { startOpenRouterSetup } from '../utils/openRouterSetup'; import { startTetrateSetup } from '../utils/tetrateSetup'; import { startChatGptCodexSetup } from '../utils/chatgptCodexSetup'; +import { startGitHubCopilotSetup } from '../utils/githubCopilotSetup'; import WelcomeGooseLogo from './WelcomeGooseLogo'; import { toastService } from '../toasts'; import { OllamaSetup } from './OllamaSetup'; @@ -20,8 +22,9 @@ import { trackOnboardingAbandoned, trackOnboardingSetupFailed, } from '../utils/analytics'; +import type { DeviceCodeResponse } from '../api/types.gen'; -import { Goose, OpenRouter, Tetrate, ChatGPT } from './icons'; +import { Goose, OpenRouter, Tetrate, ChatGPT, GitHubCopilot } from './icons'; interface ProviderGuardProps { didSelectProvider: boolean; @@ -80,6 +83,13 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG autoClose?: number; } | null>(null); + const [githubCopilotDeviceCodeState, setGithubCopilotDeviceCodeState] = useState<{ + show: boolean; + deviceCodeData?: DeviceCodeResponse; + }>({ + show: false, + }); + const handleTetrateSetup = async () => { trackOnboardingProviderSelected('tetrate'); try { @@ -136,6 +146,59 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG } }; + const handleGitHubCopilotSetup = async () => { + trackOnboardingProviderSelected('github_copilot'); + try { + const result = await startGitHubCopilotSetup(); + if (result.success) { + if (result.data) { + // Device code flow - show device code modal + setGithubCopilotDeviceCodeState({ + show: true, + deviceCodeData: result.data, + }); + } else { + // Already configured or completed + setSwitchModelProvider('github_copilot'); + setShowSwitchModelModal(true); + } + } else { + trackOnboardingSetupFailed('github_copilot', result.message); + setChatgptCodexSetupState({ + show: true, + title: 'Setup Failed', + message: result.message, + showRetry: true, + }); + } + } catch (error) { + console.error('GitHub Copilot setup error:', error); + trackOnboardingSetupFailed('github_copilot', 'unexpected_error'); + setChatgptCodexSetupState({ + show: true, + title: 'Setup Error', + message: 'An unexpected error occurred during setup.', + showRetry: true, + }); + } + }; + + const handleDeviceCodeComplete = () => { + setGithubCopilotDeviceCodeState({ show: false }); + setSwitchModelProvider('github_copilot'); + setShowSwitchModelModal(true); + }; + + const handleDeviceCodeCancel = () => { + setGithubCopilotDeviceCodeState({ show: false }); + trackOnboardingAbandoned('github_copilot_setup'); + }; + + const handleDeviceCodeRetry = async () => { + setGithubCopilotDeviceCodeState({ show: false }); + await handleGitHubCopilotSetup(); + }; + const handleApiKeySuccess = async (provider: string, _model: string, apiKey: string) => { trackOnboardingProviderSelected('api_key'); const keyName = `${provider.toUpperCase()}_API_KEY`; @@ -431,6 +494,41 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG + {/* GitHub Copilot Card - Full Width */} +
+
+
+
+ + + GitHub Copilot + +
+
+ + + +
+
+

+ Use GitHub Copilot access for a variety of AI models. +

+
+
+ {/* Tetrate Card - Full Width */}
@@ -582,6 +680,16 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG /> )} + {githubCopilotDeviceCodeState.show && ( + + )} + {showSwitchModelModal && ( + + + ); +} diff --git a/ui/desktop/src/components/icons/index.tsx b/ui/desktop/src/components/icons/index.tsx index c648d2ce6348..b4890f6271c0 100644 --- a/ui/desktop/src/components/icons/index.tsx +++ b/ui/desktop/src/components/icons/index.tsx @@ -3,6 +3,7 @@ import ArrowUp from './ArrowUp'; import Attach from './Attach'; import Back from './Back'; import ChatGPT from './ChatGPT'; +import GitHubCopilot from './GitHubCopilot'; import { Bird1 } from './Bird1'; import { Bird2 } from './Bird2'; import { Bird3 } from './Bird3'; @@ -61,6 +62,7 @@ export { Attach, Back, ChatGPT, + GitHubCopilot, Bird1, Bird2, Bird3, diff --git a/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx b/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx index 2e0e9ccab02f..f4b6cacf1cb5 100644 --- a/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx +++ b/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx @@ -18,6 +18,8 @@ import { useConfig } from '../../../ConfigContext'; import { useModelAndProvider } from '../../../ModelAndProviderContext'; import { AlertTriangle, LogIn } from 'lucide-react'; import { ProviderDetails, removeCustomProvider, configureProviderOauth } from '../../../../api'; +import type { DeviceCodeResponse } from '../../../../api/types.gen'; +import { DeviceCodeModal } from '../../../DeviceCodeModal'; import { Button } from '../../../../components/ui/button'; import { errorMessage } from '../../../../utils/conversionUtils'; @@ -40,6 +42,7 @@ export default function ProviderConfigurationModal({ const [isActiveProvider, setIsActiveProvider] = useState(false); const [error, setError] = useState(null); const [isOAuthLoading, setIsOAuthLoading] = useState(false); + const [deviceCodeData, setDeviceCodeData] = useState(undefined); let primaryParameters = provider.metadata.config_keys.filter((param) => param.primary); if (primaryParameters.length === 0) { @@ -66,13 +69,23 @@ export default function ProviderConfigurationModal({ setIsOAuthLoading(true); setError(null); try { - await configureProviderOauth({ + const response = await configureProviderOauth({ path: { name: provider.name }, }); - if (onConfigured) { - onConfigured(provider); + + const data = response.data; + + // Check if response is a device code response (has userCode field) + if (data && 'userCode' in data && 'verificationUri' in data) { + // Device code flow - show device code modal + setDeviceCodeData(data); } else { - onClose(); + // Completed flow + if (onConfigured) { + onConfigured(provider); + } else { + onClose(); + } } } catch (err) { setError(`OAuth login failed: ${errorMessage(err)}`); @@ -81,6 +94,15 @@ export default function ProviderConfigurationModal({ } }; + const handleDeviceCodeCancel = () => { + setDeviceCodeData(undefined); + }; + + const handleDeviceCodeRetry = () => { + setDeviceCodeData(undefined); + handleOAuthLogin(); + }; + const handleSubmitForm = async (e: React.FormEvent) => { e.preventDefault(); @@ -198,7 +220,7 @@ export default function ProviderConfigurationModal({ - !open && onClose()}> + !open && onClose()}> @@ -279,6 +301,22 @@ export default function ProviderConfigurationModal({ + {deviceCodeData && ( + { + setDeviceCodeData(undefined); + if (onConfigured) { + onConfigured(provider); + } else { + onClose(); + } + }} + onCancel={handleDeviceCodeCancel} + onRetry={handleDeviceCodeRetry} + /> + )} ); } diff --git a/ui/desktop/src/hooks/useChatStream.ts b/ui/desktop/src/hooks/useChatStream.ts index 03eca84814fa..4c270949a44e 100644 --- a/ui/desktop/src/hooks/useChatStream.ts +++ b/ui/desktop/src/hooks/useChatStream.ts @@ -612,7 +612,14 @@ export function useChatStream({ sseMaxRetryAttempts: 0, }); - await streamFromResponse(stream, currentMessages, dispatch, onFinish, sessionId, abortControllerRef.current.signal); + await streamFromResponse( + stream, + currentMessages, + dispatch, + onFinish, + sessionId, + abortControllerRef.current.signal + ); } catch (error) { // AbortError is expected when user stops streaming if (error instanceof Error && error.name === 'AbortError') { @@ -654,7 +661,14 @@ export function useChatStream({ sseMaxRetryAttempts: 0, }); - await streamFromResponse(stream, currentMessages, dispatch, onFinish, sessionId, abortControllerRef.current.signal); + await streamFromResponse( + stream, + currentMessages, + dispatch, + onFinish, + sessionId, + abortControllerRef.current.signal + ); } catch (error) { if (error instanceof Error && error.name === 'AbortError') { // Silently handle abort @@ -794,7 +808,14 @@ export function useChatStream({ sseMaxRetryAttempts: 0, }); - await streamFromResponse(stream, messagesForUI, dispatch, onFinish, targetSessionId, abortControllerRef.current.signal); + await streamFromResponse( + stream, + messagesForUI, + dispatch, + onFinish, + targetSessionId, + abortControllerRef.current.signal + ); } catch (error) { if (error instanceof Error && error.name === 'AbortError') { dispatch({ type: 'SET_CHAT_STATE', payload: ChatState.Idle }); diff --git a/ui/desktop/src/utils/analytics.ts b/ui/desktop/src/utils/analytics.ts index 4e85690291a1..fddc4f8a035b 100644 --- a/ui/desktop/src/utils/analytics.ts +++ b/ui/desktop/src/utils/analytics.ts @@ -75,6 +75,7 @@ export type AnalyticsEvent = | 'openrouter' | 'tetrate' | 'chatgpt_codex' + | 'github_copilot' | 'ollama' | 'local' | 'other'; @@ -88,7 +89,7 @@ export type AnalyticsEvent = | { name: 'onboarding_setup_failed'; properties: { - provider: 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'local'; + provider: 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'github_copilot' | 'local'; error_message?: string; }; } @@ -294,7 +295,15 @@ export function trackOnboardingStarted(): void { } export function trackOnboardingProviderSelected( - method: 'api_key' | 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'ollama' | 'local' | 'other' + method: + | 'api_key' + | 'openrouter' + | 'tetrate' + | 'chatgpt_codex' + | 'github_copilot' + | 'ollama' + | 'local' + | 'other' ): void { trackEvent({ name: 'onboarding_provider_selected', @@ -327,7 +336,7 @@ export function trackOnboardingAbandoned(step: string): void { } export function trackOnboardingSetupFailed( - provider: 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'local', + provider: 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'github_copilot' | 'local', errorMessage?: string ): void { trackEvent({ diff --git a/ui/desktop/src/utils/githubCopilotSetup.ts b/ui/desktop/src/utils/githubCopilotSetup.ts new file mode 100644 index 000000000000..5f59e555466c --- /dev/null +++ b/ui/desktop/src/utils/githubCopilotSetup.ts @@ -0,0 +1,38 @@ +import { configureProviderOauth } from '../api'; +import type { OauthResponse, DeviceCodeResponse } from '../api/types.gen'; + +export async function startGitHubCopilotSetup(): Promise<{ + success: boolean; + data?: DeviceCodeResponse; + message: string; +}> { + try { + const response = await configureProviderOauth({ + path: { name: 'github_copilot' }, + throwOnError: true, + }); + + // Handle the discriminated union response + const data = response.data as OauthResponse; + + if ('userCode' in data && 'verificationUri' in data) { + // Device code response - user needs to enter code manually + return { + success: true, + data: data, + message: 'Device code received, please enter code on GitHub', + }; + } else { + // Completed response + return { + success: true, + message: 'GitHub Copilot setup completed', + }; + } + } catch (e) { + return { + success: false, + message: `Failed to start GitHub Copilot setup: ${e}`, + }; + } +}