From 2fda760641b634ee915a3b219dd083cf77b2ea2a Mon Sep 17 00:00:00 2001 From: Alex Hancock Date: Fri, 26 Jun 2026 13:04:24 -0400 Subject: [PATCH] feat(acp+): models/providers in desktop on ACP+ (#9987) --- Cargo.lock | 4 +- crates/goose-mcp/Cargo.toml | 2 +- crates/goose-sdk-types/src/custom_requests.rs | 107 +++ crates/goose-server/src/openapi.rs | 1 - .../src/routes/config_management.rs | 827 +--------------- crates/goose/acp-meta.json | 20 + crates/goose/acp-schema.json | 266 ++++++ crates/goose/src/acp/server/config.rs | 14 + .../goose/src/acp/server/custom_dispatch.rs | 32 + crates/goose/src/acp/server/providers.rs | 122 ++- crates/goose/src/config/mod.rs | 4 +- crates/goose/src/config/providers.rs | 107 +-- crates/goose/src/providers/mod.rs | 1 + .../goose/src/providers/provider_secrets.rs | 651 +++++++++++++ ui/desktop/openapi.json | 27 - ui/desktop/src/App.test.tsx | 9 + ui/desktop/src/acp/providers.ts | 217 +++++ ui/desktop/src/api/index.ts | 4 +- ui/desktop/src/api/sdk.gen.ts | 4 +- ui/desktop/src/api/types.gen.ts | 26 - ui/desktop/src/components/ChatInput.tsx | 9 +- ui/desktop/src/components/ConfigContext.tsx | 9 +- .../components/ModelAndProviderContext.tsx | 66 +- .../components/bottom_menu/CostTracker.tsx | 15 +- .../components/onboarding/OnboardingGuard.tsx | 22 +- .../onboarding/ProviderConfigForm.tsx | 16 +- .../onboarding/ProviderSelector.tsx | 24 +- .../recipes/shared/RecipeModelSelector.tsx | 7 +- .../auth/AuthSettingsSection.test.tsx | 126 +-- .../settings/auth/AuthSettingsSection.tsx | 54 +- .../settings/auth/HuggingFaceSignInPrompt.tsx | 13 +- .../localInference/LocalInferenceSettings.tsx | 7 +- .../settings/models/ModelsSection.tsx | 18 +- .../models/bottom_bar/ModelsBottomBar.tsx | 34 +- .../settings/models/modelInterface.ts | 35 +- .../models/subcomponents/SwitchModelModal.tsx | 59 +- .../settings/providers/ProviderGrid.tsx | 54 +- .../providers/ProviderSettingsPage.tsx | 12 +- .../modal/ProviderConfigurationModal.tsx | 58 +- .../subcomponents/ProviderCatalogPicker.tsx | 62 +- .../forms/CustomProviderForm.tsx | 81 +- .../forms/DefaultProviderSetupForm.tsx | 27 +- .../handlers/DefaultSubmitHandler.tsx | 111 +-- .../reset_provider/ResetProviderSection.tsx | 6 +- ui/desktop/src/utils/canonical.ts | 13 +- ui/sdk/src/generated/client.gen.ts | 53 ++ ui/sdk/src/generated/index.ts | 22 +- ui/sdk/src/generated/types.gen.ts | 73 +- ui/sdk/src/generated/zod.gen.ts | 890 ++++++++++++++---- 49 files changed, 2734 insertions(+), 1687 deletions(-) create mode 100644 crates/goose/src/providers/provider_secrets.rs create mode 100644 ui/desktop/src/acp/providers.ts diff --git a/Cargo.lock b/Cargo.lock index a3b28d823..d20de2b52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6450,9 +6450,9 @@ checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lopdf" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67513274c50a2b51e5f75d9e682fcf4ab064a8a9c9ae2c3c59309084882bb24d" +checksum = "25aab26d99567469098e64a02f42679f8965c6401263eefa31d8f2dcc37a221c" dependencies = [ "aes", "bitflags 2.11.1", diff --git a/crates/goose-mcp/Cargo.toml b/crates/goose-mcp/Cargo.toml index 45ac8577f..07335e821 100644 --- a/crates/goose-mcp/Cargo.toml +++ b/crates/goose-mcp/Cargo.toml @@ -35,7 +35,7 @@ etcetera = { workspace = true } tempfile = { workspace = true } include_dir = { workspace = true } once_cell = { workspace = true } -lopdf = { version = "0.41", default-features = false } +lopdf = { version = "0.42", default-features = false } docx-rs = { version = "0.4.18", default-features = false, features = ["image"] } image = { version = "0.24.4", default-features = false, features = ["bmp", "dds", "dxt", "farbfeld", "gif", "hdr", "ico", "jpeg", "jpeg_rayon", "openexr", "png", "pnm", "tga", "tiff", "webp"] } umya-spreadsheet = { version = "2", default-features = false } diff --git a/crates/goose-sdk-types/src/custom_requests.rs b/crates/goose-sdk-types/src/custom_requests.rs index 91950d549..9b3f99de3 100644 --- a/crates/goose-sdk-types/src/custom_requests.rs +++ b/crates/goose-sdk-types/src/custom_requests.rs @@ -527,6 +527,12 @@ pub struct DefaultsSaveRequest { pub model_id: Option, } +/// Clear Goose default provider and model configuration. +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] +#[request(method = "_goose/unstable/defaults/clear", response = DefaultsReadResponse)] +#[serde(rename_all = "camelCase")] +pub struct DefaultsClearRequest {} + /// Sources that onboarding knows how to discover and import. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -862,6 +868,107 @@ pub struct ProviderConfigChangeResponse { pub refresh: RefreshProviderInventoryResponse, } +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ProviderSecretStorageDto { + #[default] + SecretStore, + ProviderCache, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ProviderSecretStatusDto { + Valid, + Expired, + #[default] + Unknown, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct ProviderSecretDto { + pub id: String, + pub provider: String, + pub provider_display_name: String, + pub name: String, + pub storage: ProviderSecretStorageDto, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires_at: Option, + pub status: ProviderSecretStatusDto, + pub configured: bool, + pub has_secret: bool, + pub can_delete: bool, + pub can_configure: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub configure_provider: Option, +} + +/// List provider credentials stored locally by Goose. +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] +#[request( + method = "_goose/unstable/providers/secrets/list", + response = ProviderSecretsListResponse +)] +#[serde(rename_all = "camelCase")] +pub struct ProviderSecretsListRequest {} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)] +#[serde(rename_all = "camelCase")] +pub struct ProviderSecretsListResponse { + pub secrets: Vec, +} + +/// Delete a locally stored provider credential by id. +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] +#[request( + method = "_goose/unstable/providers/secrets/delete", + response = EmptyResponse +)] +#[serde(rename_all = "camelCase")] +pub struct ProviderSecretDeleteRequest { + pub id: String, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct CanonicalModelInfoDto { + pub provider: String, + pub model: String, + pub context_limit: usize, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_output_tokens: Option, + pub reasoning: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub input_token_cost: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub output_token_cost: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_read_token_cost: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_write_token_cost: Option, + pub currency: String, +} + +/// Look up canonical (bundled-registry) model info for a provider/model pair. +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] +#[request( + method = "_goose/unstable/providers/canonical-model-info", + response = CanonicalModelInfoResponse +)] +#[serde(rename_all = "camelCase")] +pub struct CanonicalModelInfoRequest { + pub provider: String, + pub model: String, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)] +#[serde(rename_all = "camelCase")] +pub struct CanonicalModelInfoResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub model_info: Option, +} + #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct ProviderTemplateCatalogEntryDto { diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index a0cc4cc7b..fd26a3558 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -418,7 +418,6 @@ derive_utoipa!(IconTheme as IconThemeSchema); super::routes::config_management::cleanup_provider_cache, super::routes::config_management::check_provider, super::routes::config_management::set_config_provider, - super::routes::config_management::configure_provider_oauth, super::routes::config_management::get_canonical_model_info, super::routes::prompts::get_prompts, super::routes::prompts::get_prompt, diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 8678f5f6d..0f65ef7aa 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -7,7 +7,6 @@ use axum::{ routing::{delete, get, post}, Json, Router, }; -use chrono::{DateTime, TimeZone, Utc}; use goose::config::declarative_providers::LoadedProvider; use goose::config::paths::Paths; use goose::config::ExtensionEntry; @@ -20,7 +19,6 @@ use goose::providers::catalog::{ ProviderTemplate, }; use goose::providers::create_with_default_model; -use goose::providers::huggingface_auth; use goose::providers::providers as get_providers; use goose::{ agents::execute_commands, agents::ExtensionConfig, slash_commands::recipe_slash_command, @@ -29,10 +27,7 @@ use goose_providers::model::ModelConfig; use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_yaml; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use utoipa::ToSchema; #[derive(Serialize, ToSchema)] @@ -134,37 +129,9 @@ pub enum ConfigValueResponse { MaskedValue(MaskedSecret), } -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ProviderSecretStorage { - SecretStore, - ProviderCache, -} - -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ProviderSecretStatus { - Valid, - Expired, - Unknown, -} - -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct ProviderSecret { - pub id: String, - pub provider: String, - pub provider_display_name: String, - pub name: String, - pub storage: ProviderSecretStorage, - pub expires_at: Option>, - pub status: ProviderSecretStatus, - pub configured: bool, - pub has_secret: bool, - pub can_delete: bool, - pub can_configure: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub configure_provider: Option, -} +pub use goose::providers::provider_secrets::{ + ProviderSecret, ProviderSecretStatus, ProviderSecretStorage, +}; #[derive(Debug, Serialize, ToSchema)] pub struct ProviderSecretsResponse { @@ -204,11 +171,8 @@ pub async fn upsert_config( ) -> Result, ErrorResponse> { let config = Config::global(); - // Intercept legacy keys to write structured provider config if query.key == "GOOSE_PROVIDER" { if let Some(name) = query.value.as_str() { - // Preserve the target provider's saved model rather than copying - // the current active provider's model into the new entry. let model = goose::config::get_provider_entry(config, name) .map(|e| e.model) .or_else(|| config.get_goose_model().ok()) @@ -278,360 +242,6 @@ fn mask_secret(secret: Value) -> String { format!("{}{}", visible, mask) } -const SECRET_STORE_ID_PREFIX: &str = "secret_store:"; -const PROVIDER_CACHE_ID_PREFIX: &str = "provider_cache:"; - -fn provider_secret_status(expires_at: Option>) -> ProviderSecretStatus { - match expires_at { - Some(expires_at) if expires_at <= Utc::now() => ProviderSecretStatus::Expired, - Some(_) => ProviderSecretStatus::Valid, - None => ProviderSecretStatus::Unknown, - } -} - -fn parse_expiry_value(value: &Value) -> Option> { - match value { - Value::String(value) => DateTime::parse_from_rfc3339(value) - .ok() - .map(|dt| dt.with_timezone(&Utc)), - Value::Number(value) => value - .as_i64() - .and_then(|timestamp| Utc.timestamp_opt(timestamp, 0).single()), - _ => None, - } -} - -fn find_expires_at(value: &Value) -> Option> { - match value { - Value::Object(map) => { - if map - .get("refresh_token") - .and_then(Value::as_str) - .is_some_and(|token| !token.is_empty()) - { - return None; - } - if let Some(expires_at) = map.get("expires_at").and_then(parse_expiry_value) { - return Some(expires_at); - } - if let Some(expires_at) = map.get("expires_on").and_then(parse_expiry_value) { - return Some(expires_at); - } - map.values().find_map(find_expires_at) - } - Value::Array(values) => values.iter().find_map(find_expires_at), - _ => None, - } -} - -#[derive(Clone, Copy)] -struct ProviderCacheSecretDefinition { - provider: &'static str, - name: &'static str, - path: &'static str, - is_directory: bool, -} - -const PROVIDER_CACHE_SECRET_DEFINITIONS: &[ProviderCacheSecretDefinition] = &[ - ProviderCacheSecretDefinition { - provider: "gemini_oauth", - name: "OAuth token", - path: "gemini_oauth/tokens.json", - is_directory: false, - }, - ProviderCacheSecretDefinition { - provider: "chatgpt_codex", - name: "OAuth token", - path: "chatgpt_codex/tokens.json", - is_directory: false, - }, - ProviderCacheSecretDefinition { - provider: "kimi_code", - name: "OAuth token", - path: "kimicode/token.json", - is_directory: false, - }, - ProviderCacheSecretDefinition { - provider: "github_copilot", - name: "OAuth token", - path: "githubcopilot", - is_directory: true, - }, - ProviderCacheSecretDefinition { - provider: "xai_oauth", - name: "OAuth token", - path: "xai_oauth/tokens.json", - is_directory: false, - }, - ProviderCacheSecretDefinition { - provider: "databricks", - name: "OAuth token", - path: "databricks/oauth", - is_directory: true, - }, - ProviderCacheSecretDefinition { - provider: "databricks_v2", - name: "OAuth token", - path: "databricks/oauth", - is_directory: true, - }, -]; - -fn provider_cache_definitions_for_display() -> Vec { - let mut seen_paths = HashSet::new(); - PROVIDER_CACHE_SECRET_DEFINITIONS - .iter() - .copied() - .filter(|definition| seen_paths.insert(definition.path)) - .collect() -} - -fn provider_cache_definition(provider: &str) -> Option { - PROVIDER_CACHE_SECRET_DEFINITIONS - .iter() - .copied() - .find(|definition| definition.provider == provider) -} - -fn provider_cache_providers_sharing_cache(provider: &str) -> Vec<&'static str> { - let Some(definition) = provider_cache_definition(provider) else { - return Vec::new(); - }; - - PROVIDER_CACHE_SECRET_DEFINITIONS - .iter() - .filter(|other| other.path == definition.path) - .map(|definition| definition.provider) - .collect() -} - -fn read_json_file(path: &std::path::Path) -> Option { - std::fs::read_to_string(path) - .ok() - .and_then(|contents| serde_json::from_str(&contents).ok()) -} - -fn collect_json_expiries(path: &std::path::Path, is_directory: bool) -> Vec> { - if !is_directory { - return read_json_file(path) - .and_then(|value| find_expires_at(&value)) - .into_iter() - .collect(); - } - - let mut expiries = Vec::new(); - let mut stack = vec![path.to_path_buf()]; - - while let Some(current) = stack.pop() { - let Ok(entries) = std::fs::read_dir(current) else { - continue; - }; - - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - stack.push(path); - continue; - } - if path.extension().and_then(|ext| ext.to_str()) != Some("json") { - continue; - } - if let Some(expires_at) = - read_json_file(&path).and_then(|value| find_expires_at(&value)) - { - expiries.push(expires_at); - } - } - } - - expiries -} - -fn provider_cache_exists(path: &std::path::Path, is_directory: bool) -> bool { - if !is_directory { - return path.is_file(); - } - - let Ok(entries) = std::fs::read_dir(path) else { - return false; - }; - - entries.flatten().any(|entry| { - let path = entry.path(); - path.is_file() || provider_cache_exists(&path, true) - }) -} - -fn provider_cache_expiry(definition: ProviderCacheSecretDefinition) -> Option> { - let path = Paths::in_config_dir(definition.path); - let expiries = collect_json_expiries(&path, definition.is_directory); - expiries.into_iter().min() -} - -fn build_provider_cache_secret( - definition: ProviderCacheSecretDefinition, - display_names: &HashMap, -) -> Option { - let path = Paths::in_config_dir(definition.path); - if !provider_cache_exists(&path, definition.is_directory) { - return None; - } - - let expires_at = provider_cache_expiry(definition); - Some(ProviderSecret { - id: format!("{}{}", PROVIDER_CACHE_ID_PREFIX, definition.provider), - provider: definition.provider.to_string(), - provider_display_name: display_names - .get(definition.provider) - .cloned() - .unwrap_or_else(|| definition.provider.to_string()), - name: definition.name.to_string(), - storage: ProviderSecretStorage::ProviderCache, - expires_at, - status: provider_secret_status(expires_at), - configured: true, - has_secret: true, - can_delete: true, - can_configure: false, - configure_provider: None, - }) -} - -fn build_huggingface_oauth_secret( - token: Option, -) -> ProviderSecret { - let expires_at = token.as_ref().and_then(|token| token.expires_at); - let has_secret = token.is_some(); - - ProviderSecret { - id: format!( - "{}{}", - PROVIDER_CACHE_ID_PREFIX, - huggingface_auth::HUGGINGFACE_PROVIDER_NAME - ), - provider: huggingface_auth::HUGGINGFACE_PROVIDER_NAME.to_string(), - provider_display_name: huggingface_auth::HUGGINGFACE_DISPLAY_NAME.to_string(), - name: huggingface_auth::HUGGINGFACE_OAUTH_TOKEN_NAME.to_string(), - storage: ProviderSecretStorage::ProviderCache, - expires_at, - status: provider_secret_status(expires_at), - configured: has_secret, - has_secret, - can_delete: has_secret, - can_configure: true, - configure_provider: Some(huggingface_auth::HUGGINGFACE_PROVIDER_NAME.to_string()), - } -} - -fn build_secret_store_secrets( - stored_secrets: &HashMap, - providers: &[(ProviderMetadata, ProviderType)], -) -> Vec { - let mut secrets = Vec::new(); - - for (metadata, _) in providers { - for config_key in metadata.config_keys.iter().filter(|key| key.secret) { - if !stored_secrets.contains_key(&config_key.name) { - continue; - } - secrets.push(ProviderSecret { - id: format!( - "{}{}:{}", - SECRET_STORE_ID_PREFIX, metadata.name, config_key.name - ), - provider: metadata.name.clone(), - provider_display_name: metadata.display_name.clone(), - name: config_key.name.clone(), - storage: ProviderSecretStorage::SecretStore, - expires_at: None, - status: ProviderSecretStatus::Unknown, - configured: true, - has_secret: true, - can_delete: true, - can_configure: false, - configure_provider: None, - }); - } - } - - secrets -} - -fn is_known_provider_secret( - providers: &[(ProviderMetadata, ProviderType)], - provider: &str, - key: &str, -) -> bool { - providers - .iter() - .filter(|(metadata, _)| metadata.name == provider) - .flat_map(|(metadata, _)| metadata.config_keys.iter()) - .any(|config_key| config_key.secret && config_key.name == key) -} - -fn unconfigure_provider(config: &Config, provider_name: &str) -> Result<(), ConfigError> { - if let Some(mut entry) = goose::config::get_provider_entry(config, provider_name) { - entry.configured = false; - goose::config::set_provider_entry(config, provider_name, &entry)?; - } - - let configured_marker = format!("{}_configured", provider_name); - config.delete(&configured_marker)?; - Ok(()) -} - -fn mark_provider_configured(config: &Config, provider_name: &str) -> Result<(), ConfigError> { - if let Some(mut entry) = goose::config::get_provider_entry(config, provider_name) { - entry.configured = true; - goose::config::set_provider_entry(config, provider_name, &entry)?; - } else { - let model = if goose::config::get_active_provider(config).as_deref() == Some(provider_name) - { - config.get_goose_model().unwrap_or_default() - } else { - String::new() - }; - goose::config::set_provider_entry( - config, - provider_name, - &goose::config::ProviderEntry { - enabled: true, - model, - configured: true, - }, - )?; - } - - Ok(()) -} - -fn parse_secret_store_id(id: &str) -> Option<(&str, &str)> { - let rest = id.strip_prefix(SECRET_STORE_ID_PREFIX)?; - let (provider, key) = rest.split_once(':')?; - Some((provider, key)) -} - -fn parse_provider_cache_id(id: &str) -> Option<&str> { - id.strip_prefix(PROVIDER_CACHE_ID_PREFIX) -} - -fn is_valid_provider_name(provider_name: &str) -> bool { - !provider_name.is_empty() - && provider_name - .chars() - .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') -} - -fn should_unconfigure_after_secret_delete( - provider: &str, - key: &str, - has_usable_huggingface_oauth_token: impl FnOnce() -> bool, -) -> bool { - provider == huggingface_auth::HUGGINGFACE_PROVIDER_NAME - && key == huggingface_auth::HUGGINGFACE_TOKEN_SECRET_KEY - && !has_usable_huggingface_oauth_token() -} - #[utoipa::path( get, path = "/config/provider-secrets", @@ -641,40 +251,7 @@ fn should_unconfigure_after_secret_delete( ) )] pub async fn list_provider_secrets() -> Result, ErrorResponse> { - let config = Config::global(); - let stored_secrets = config.all_secrets()?; - let providers = get_providers().await; - let display_names: HashMap = providers - .iter() - .map(|(metadata, _)| (metadata.name.clone(), metadata.display_name.clone())) - .collect(); - - let mut secrets = build_secret_store_secrets(&stored_secrets, &providers); - - for definition in provider_cache_definitions_for_display() { - if let Some(secret) = build_provider_cache_secret(definition, &display_names) { - if !secrets.iter().any(|existing| existing.id == secret.id) { - secrets.push(secret); - } - } - } - - let huggingface_secret = build_huggingface_oauth_secret(huggingface_auth::load_oauth_token()); - if let Some(existing) = secrets - .iter_mut() - .find(|existing| existing.id == huggingface_secret.id) - { - *existing = huggingface_secret; - } else { - secrets.push(huggingface_secret); - } - - secrets.sort_by(|a, b| { - a.provider_display_name - .cmp(&b.provider_display_name) - .then_with(|| a.name.cmp(&b.name)) - }); - + let secrets = goose::providers::provider_secrets::list_provider_secrets().await?; Ok(Json(ProviderSecretsResponse { secrets })) } @@ -691,52 +268,17 @@ pub async fn list_provider_secrets() -> Result, Er ) )] pub async fn delete_provider_secret(Path(id): Path) -> Result, ErrorResponse> { - let config = Config::global(); + use goose::providers::provider_secrets::DeleteProviderSecretError; - if let Some((provider, key)) = parse_secret_store_id(&id) { - let providers = get_providers().await; - if !is_known_provider_secret(&providers, provider, key) { - return Err(ErrorResponse::bad_request(format!( - "Invalid provider secret id: '{}'", - id - ))); - } - - config.delete_secret(key)?; - if should_unconfigure_after_secret_delete(provider, key, || { - huggingface_auth::has_configured_token().unwrap_or(false) - }) { - unconfigure_provider(config, provider)?; - } - return Ok(Json(format!("Deleted provider secret {}", id))); + match goose::providers::provider_secrets::delete_provider_secret(&id).await { + Ok(()) => Ok(Json(format!("Deleted provider secret {}", id))), + Err(DeleteProviderSecretError::InvalidId(id)) => Err(ErrorResponse::bad_request(format!( + "Invalid provider secret id: '{}'", + id + ))), + Err(DeleteProviderSecretError::Config(e)) => Err(e.into()), + Err(DeleteProviderSecretError::Other(e)) => Err(ErrorResponse::internal(e.to_string())), } - - if let Some(provider) = parse_provider_cache_id(&id) { - if provider == huggingface_auth::HUGGINGFACE_PROVIDER_NAME { - huggingface_auth::clear_oauth_token()?; - unconfigure_provider(config, provider)?; - return Ok(Json(format!("Deleted provider secret {}", id))); - } - - let cache_definition = provider_cache_definition(provider); - - if !is_valid_provider_name(provider) || cache_definition.is_none() { - return Err(ErrorResponse::bad_request(format!( - "Invalid provider name: '{}'", - provider - ))); - } - goose::providers::cleanup_provider(provider).await?; - for shared_provider in provider_cache_providers_sharing_cache(provider) { - unconfigure_provider(config, shared_provider)?; - } - return Ok(Json(format!("Deleted provider secret {}", id))); - } - - Err(ErrorResponse::bad_request(format!( - "Invalid provider secret id: '{}'", - id - ))) } #[utoipa::path( @@ -753,7 +295,6 @@ pub async fn read_config( ) -> Result, ErrorResponse> { let config = Config::global(); - // Intercept legacy keys to return structured provider config if query.key == "GOOSE_PROVIDER" || query.key == "active_provider" { if let Ok(val) = config.get_goose_provider() { return Ok(Json(ConfigValueResponse::Value(Value::String(val)))); @@ -1288,7 +829,6 @@ pub async fn update_custom_provider( pub async fn check_provider( Json(CheckProviderRequest { provider }): Json, ) -> Result<(), ErrorResponse> { - // Provider check does not use extensions. create_with_default_model(&provider, Vec::new()) .await .map_err(|err| { @@ -1305,7 +845,6 @@ pub async fn check_provider( pub async fn set_config_provider( Json(SetProviderRequest { provider, model }): Json, ) -> Result<(), ErrorResponse> { - // Provider validation does not use extensions. create_with_default_model(&provider, Vec::new()) .await .and_then(|_| { @@ -1370,60 +909,6 @@ pub async fn get_provider_catalog_template( Ok(Json(template)) } -#[utoipa::path( - post, - path = "/config/providers/{name}/oauth", - params( - ("name" = String, Path, description = "Provider name") - ), - responses( - (status = 200, description = "OAuth configuration completed"), - (status = 400, description = "OAuth configuration failed") - ) -)] -pub async fn configure_provider_oauth( - Path(provider_name): Path, -) -> Result, ErrorResponse> { - use goose::providers::create; - - if !is_valid_provider_name(&provider_name) { - return Err(ErrorResponse::bad_request(format!( - "Invalid provider name: '{}'", - provider_name - ))); - } - - if provider_name == huggingface_auth::HUGGINGFACE_PROVIDER_NAME { - huggingface_auth::configure_oauth().await.map_err(|e| { - ErrorResponse::bad_request(format!( - "OAuth configuration failed for provider '{}': {}", - provider_name, e - )) - })?; - mark_provider_configured(goose::config::Config::global(), &provider_name)?; - return Ok(Json("OAuth configuration completed".to_string())); - } - - // OAuth configuration does not use extensions. - let provider = create(&provider_name, Vec::new()).await.map_err(|e| { - ErrorResponse::bad_request(format!( - "Failed to create provider '{}': {}", - provider_name, e - )) - })?; - - provider.configure_oauth().await.map_err(|e| { - ErrorResponse::bad_request(format!( - "OAuth configuration failed for provider '{}': {}", - provider_name, e - )) - })?; - - mark_provider_configured(goose::config::Config::global(), &provider_name)?; - - Ok(Json("OAuth configuration completed".to_string())) -} - pub fn routes(state: Arc) -> Router { Router::new() .route("/config", get(read_all_config)) @@ -1468,289 +953,5 @@ 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) } - -#[cfg(test)] -mod tests { - use super::*; - use goose::config::ProviderEntry; - use goose::providers::base::ConfigKey; - use serde_json::json; - - fn new_test_config() -> Config { - let unique = format!( - "goose-server-config-test-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - ); - let config_path = std::env::temp_dir().join(format!("{unique}-config.yaml")); - let secrets_path = std::env::temp_dir().join(format!("{unique}-secrets.yaml")); - Config::new_with_file_secrets(config_path, secrets_path).unwrap() - } - - #[test] - fn secret_store_listing_only_includes_provider_secret_keys() { - let metadata = ProviderMetadata::new( - "openai", - "OpenAI", - "OpenAI provider", - "gpt-4o", - vec![], - "https://example.com", - vec![ - ConfigKey::new("OPENAI_API_KEY", true, true, None, true), - ConfigKey::new("OPENAI_HOST", false, false, None, false), - ], - ); - let providers = vec![(metadata, ProviderType::Builtin)]; - let stored_secrets = HashMap::from([ - ( - "OPENAI_API_KEY".to_string(), - Value::String("secret-value".to_string()), - ), - ( - "UNRELATED_SECRET".to_string(), - Value::String("other-secret".to_string()), - ), - ( - "OPENAI_HOST".to_string(), - Value::String("https://api.openai.com".to_string()), - ), - ]); - - let secrets = build_secret_store_secrets(&stored_secrets, &providers); - - assert_eq!(secrets.len(), 1); - assert_eq!(secrets[0].id, "secret_store:openai:OPENAI_API_KEY"); - assert_eq!(secrets[0].provider_display_name, "OpenAI"); - assert_eq!(secrets[0].name, "OPENAI_API_KEY"); - assert_eq!(secrets[0].storage, ProviderSecretStorage::SecretStore); - assert_eq!(secrets[0].status, ProviderSecretStatus::Unknown); - } - - #[test] - fn provider_secret_delete_validation_requires_provider_secret_key() { - let metadata = ProviderMetadata::new( - "openai", - "OpenAI", - "OpenAI provider", - "gpt-4o", - vec![], - "https://example.com", - vec![ - ConfigKey::new("OPENAI_API_KEY", true, true, None, true), - ConfigKey::new("OPENAI_HOST", false, false, None, false), - ], - ); - let providers = vec![(metadata, ProviderType::Builtin)]; - - assert!(is_known_provider_secret( - &providers, - "openai", - "OPENAI_API_KEY" - )); - assert!(!is_known_provider_secret( - &providers, - "openai", - "OPENAI_HOST" - )); - assert!(!is_known_provider_secret( - &providers, - "openai", - "UNRELATED_SECRET" - )); - assert!(!is_known_provider_secret( - &providers, - "anthropic", - "OPENAI_API_KEY" - )); - } - - #[test] - fn expiry_extraction_handles_nested_rfc3339_values() { - let expires_at = Utc::now() + chrono::Duration::hours(1); - let value = json!({ - "project_id": "project", - "token": { - "access_token": "secret", - "expires_at": expires_at.to_rfc3339(), - } - }); - - let parsed = find_expires_at(&value).expect("expected expiry"); - - assert_eq!(parsed.timestamp(), expires_at.timestamp()); - assert_eq!( - provider_secret_status(Some(parsed)), - ProviderSecretStatus::Valid - ); - } - - #[test] - fn expiry_extraction_ignores_refreshable_access_tokens() { - let expires_at = Utc::now() - chrono::Duration::hours(1); - let value = json!({ - "access_token": "access", - "refresh_token": "refresh", - "expires_at": expires_at.to_rfc3339(), - }); - - assert_eq!(find_expires_at(&value), None); - } - - #[test] - fn expiry_extraction_handles_expired_unix_timestamps() { - let value = json!({ - "info": { - "expires_at": 1 - } - }); - - let parsed = find_expires_at(&value).expect("expected expiry"); - - assert_eq!(parsed.timestamp(), 1); - assert_eq!( - provider_secret_status(Some(parsed)), - ProviderSecretStatus::Expired - ); - } - - #[test] - fn provider_secret_ids_parse_expected_prefixes() { - assert_eq!( - parse_secret_store_id("secret_store:openai:OPENAI_API_KEY"), - Some(("openai", "OPENAI_API_KEY")) - ); - assert_eq!( - parse_provider_cache_id("provider_cache:gemini_oauth"), - Some("gemini_oauth") - ); - assert_eq!(parse_secret_store_id("provider_cache:openai"), None); - assert_eq!(parse_provider_cache_id("secret_store:openai:key"), None); - } - - #[test] - fn shared_databricks_cache_is_displayed_once() { - let databricks_definitions: Vec<_> = provider_cache_definitions_for_display() - .into_iter() - .filter(|definition| definition.path == "databricks/oauth") - .collect(); - - assert_eq!(databricks_definitions.len(), 1); - assert_eq!(databricks_definitions[0].provider, "databricks"); - } - - #[test] - fn shared_databricks_cache_unconfigures_both_providers() { - assert_eq!( - provider_cache_providers_sharing_cache("databricks"), - vec!["databricks", "databricks_v2"] - ); - assert_eq!( - provider_cache_providers_sharing_cache("databricks_v2"), - vec!["databricks", "databricks_v2"] - ); - } - - #[test] - fn unconfigure_provider_clears_structured_entry() { - let config = new_test_config(); - goose::config::set_provider_entry( - &config, - "huggingface", - &ProviderEntry { - enabled: true, - model: "Qwen/Qwen3-Coder-480B-A35B-Instruct".to_string(), - configured: true, - }, - ) - .unwrap(); - - unconfigure_provider(&config, "huggingface").unwrap(); - - let entry = goose::config::get_provider_entry(&config, "huggingface").unwrap(); - assert!(entry.enabled); - assert_eq!(entry.model, "Qwen/Qwen3-Coder-480B-A35B-Instruct"); - assert!(!entry.configured); - } - - #[test] - fn unconfigure_provider_deletes_legacy_configured_marker() { - let config = new_test_config(); - config.set_param("huggingface_configured", true).unwrap(); - - unconfigure_provider(&config, "huggingface").unwrap(); - - assert!(config.get_param::("huggingface_configured").is_err()); - } - - #[test] - fn deleting_huggingface_token_unconfigures_without_oauth() { - assert!(should_unconfigure_after_secret_delete( - "huggingface", - "HF_TOKEN", - || false - )); - } - - #[test] - fn deleting_huggingface_token_keeps_configured_with_oauth() { - assert!(!should_unconfigure_after_secret_delete( - "huggingface", - "HF_TOKEN", - || true - )); - } - - #[test] - fn deleting_other_provider_secret_does_not_unconfigure_huggingface() { - assert!(!should_unconfigure_after_secret_delete( - "openai", - "OPENAI_API_KEY", - || false - )); - } - - #[test] - fn huggingface_oauth_secret_is_permanent_without_token() { - let secret = build_huggingface_oauth_secret(None); - - assert_eq!(secret.id, "provider_cache:huggingface"); - assert_eq!(secret.provider_display_name, "Hugging Face"); - assert_eq!(secret.name, "OAuth token"); - assert_eq!(secret.storage, ProviderSecretStorage::ProviderCache); - assert_eq!(secret.status, ProviderSecretStatus::Unknown); - assert!(!secret.configured); - assert!(!secret.has_secret); - assert!(!secret.can_delete); - assert!(secret.can_configure); - assert_eq!(secret.configure_provider.as_deref(), Some("huggingface")); - } - - #[test] - fn huggingface_oauth_secret_reports_cached_token_metadata() { - let expires_at = Utc::now() + chrono::Duration::hours(1); - let secret = build_huggingface_oauth_secret(Some(huggingface_auth::HuggingFaceTokenData { - access_token: "hidden".to_string(), - refresh_token: None, - expires_at: Some(expires_at), - })); - - assert_eq!( - secret.expires_at.map(|value| value.timestamp()), - Some(expires_at.timestamp()) - ); - assert_eq!(secret.status, ProviderSecretStatus::Valid); - assert!(secret.configured); - assert!(secret.has_secret); - assert!(secret.can_delete); - } -} diff --git a/crates/goose/acp-meta.json b/crates/goose/acp-meta.json index af5fd271c..f801cf512 100644 --- a/crates/goose/acp-meta.json +++ b/crates/goose/acp-meta.json @@ -195,6 +195,21 @@ "requestType": "ProviderConfigAuthenticateRequest_unstable", "responseType": "ProviderConfigChangeResponse_unstable" }, + { + "method": "_goose/unstable/providers/secrets/list", + "requestType": "ProviderSecretsListRequest_unstable", + "responseType": "ProviderSecretsListResponse_unstable" + }, + { + "method": "_goose/unstable/providers/secrets/delete", + "requestType": "ProviderSecretDeleteRequest_unstable", + "responseType": "EmptyResponse" + }, + { + "method": "_goose/unstable/providers/canonical-model-info", + "requestType": "CanonicalModelInfoRequest_unstable", + "responseType": "CanonicalModelInfoResponse_unstable" + }, { "method": "_goose/unstable/preferences/read", "requestType": "PreferencesReadRequest_unstable", @@ -220,6 +235,11 @@ "requestType": "DefaultsSaveRequest_unstable", "responseType": "DefaultsReadResponse_unstable" }, + { + "method": "_goose/unstable/defaults/clear", + "requestType": "DefaultsClearRequest_unstable", + "responseType": "DefaultsReadResponse_unstable" + }, { "method": "_goose/unstable/onboarding/import/scan", "requestType": "OnboardingImportScanRequest_unstable", diff --git a/crates/goose/acp-schema.json b/crates/goose/acp-schema.json index 28e08ce01..7debc5185 100644 --- a/crates/goose/acp-schema.json +++ b/crates/goose/acp-schema.json @@ -2668,6 +2668,214 @@ "x-side": "agent", "x-method": "_goose/unstable/providers/config/authenticate" }, + "ProviderSecretsListRequest_unstable": { + "type": "object", + "description": "List provider credentials stored locally by Goose.", + "x-side": "agent", + "x-method": "_goose/unstable/providers/secrets/list" + }, + "ProviderSecretsListResponse_unstable": { + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/$defs/ProviderSecretDto" + } + } + }, + "required": [ + "secrets" + ], + "x-side": "agent", + "x-method": "_goose/unstable/providers/secrets/list" + }, + "ProviderSecretDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "providerDisplayName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "storage": { + "$ref": "#/$defs/ProviderSecretStorageDto" + }, + "expiresAt": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/$defs/ProviderSecretStatusDto" + }, + "configured": { + "type": "boolean" + }, + "hasSecret": { + "type": "boolean" + }, + "canDelete": { + "type": "boolean" + }, + "canConfigure": { + "type": "boolean" + }, + "configureProvider": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "provider", + "providerDisplayName", + "name", + "storage", + "status", + "configured", + "hasSecret", + "canDelete", + "canConfigure" + ] + }, + "ProviderSecretStorageDto": { + "type": "string", + "enum": [ + "secret_store", + "provider_cache" + ] + }, + "ProviderSecretStatusDto": { + "type": "string", + "enum": [ + "valid", + "expired", + "unknown" + ] + }, + "ProviderSecretDeleteRequest_unstable": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "description": "Delete a locally stored provider credential by id.", + "x-side": "agent", + "x-method": "_goose/unstable/providers/secrets/delete" + }, + "CanonicalModelInfoRequest_unstable": { + "type": "object", + "properties": { + "provider": { + "type": "string" + }, + "model": { + "type": "string" + } + }, + "required": [ + "provider", + "model" + ], + "description": "Look up canonical (bundled-registry) model info for a provider/model pair.", + "x-side": "agent", + "x-method": "_goose/unstable/providers/canonical-model-info" + }, + "CanonicalModelInfoResponse_unstable": { + "type": "object", + "properties": { + "modelInfo": { + "anyOf": [ + { + "$ref": "#/$defs/CanonicalModelInfoDto" + }, + { + "type": "null" + } + ] + } + }, + "x-side": "agent", + "x-method": "_goose/unstable/providers/canonical-model-info" + }, + "CanonicalModelInfoDto": { + "type": "object", + "properties": { + "provider": { + "type": "string" + }, + "model": { + "type": "string" + }, + "contextLimit": { + "type": "integer", + "minimum": 0 + }, + "maxOutputTokens": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "reasoning": { + "type": "boolean" + }, + "inputTokenCost": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "outputTokenCost": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "cacheReadTokenCost": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "cacheWriteTokenCost": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "currency": { + "type": "string" + } + }, + "required": [ + "provider", + "model", + "contextLimit", + "reasoning", + "currency" + ] + }, "PreferencesReadRequest_unstable": { "type": "object", "properties": { @@ -2797,6 +3005,12 @@ "x-side": "agent", "x-method": "_goose/unstable/defaults/save" }, + "DefaultsClearRequest_unstable": { + "type": "object", + "description": "Clear Goose default provider and model configuration.", + "x-side": "agent", + "x-method": "_goose/unstable/defaults/clear" + }, "OnboardingImportScanRequest_unstable": { "type": "object", "properties": { @@ -5775,6 +5989,33 @@ "description": "Params for _goose/unstable/providers/config/authenticate", "title": "ProviderConfigAuthenticateRequest_unstable" }, + { + "allOf": [ + { + "$ref": "#/$defs/ProviderSecretsListRequest_unstable" + } + ], + "description": "Params for _goose/unstable/providers/secrets/list", + "title": "ProviderSecretsListRequest_unstable" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ProviderSecretDeleteRequest_unstable" + } + ], + "description": "Params for _goose/unstable/providers/secrets/delete", + "title": "ProviderSecretDeleteRequest_unstable" + }, + { + "allOf": [ + { + "$ref": "#/$defs/CanonicalModelInfoRequest_unstable" + } + ], + "description": "Params for _goose/unstable/providers/canonical-model-info", + "title": "CanonicalModelInfoRequest_unstable" + }, { "allOf": [ { @@ -5820,6 +6061,15 @@ "description": "Params for _goose/unstable/defaults/save", "title": "DefaultsSaveRequest_unstable" }, + { + "allOf": [ + { + "$ref": "#/$defs/DefaultsClearRequest_unstable" + } + ], + "description": "Params for _goose/unstable/defaults/clear", + "title": "DefaultsClearRequest_unstable" + }, { "allOf": [ { @@ -6523,6 +6773,22 @@ ], "title": "ProviderConfigChangeResponse_unstable" }, + { + "allOf": [ + { + "$ref": "#/$defs/ProviderSecretsListResponse_unstable" + } + ], + "title": "ProviderSecretsListResponse_unstable" + }, + { + "allOf": [ + { + "$ref": "#/$defs/CanonicalModelInfoResponse_unstable" + } + ], + "title": "CanonicalModelInfoResponse_unstable" + }, { "allOf": [ { diff --git a/crates/goose/src/acp/server/config.rs b/crates/goose/src/acp/server/config.rs index 654af78c4..14a95a150 100644 --- a/crates/goose/src/acp/server/config.rs +++ b/crates/goose/src/acp/server/config.rs @@ -127,6 +127,20 @@ impl GooseAcpAgent { model_id, }) } + + pub(super) async fn on_defaults_clear( + &self, + _req: DefaultsClearRequest, + ) -> Result { + let config = self.config()?; + crate::config::clear_active_provider(config) + .internal_err_ctx("Failed to clear default provider")?; + + Ok(DefaultsReadResponse { + provider_id: None, + model_id: None, + }) + } } struct PreferenceDef { diff --git a/crates/goose/src/acp/server/custom_dispatch.rs b/crates/goose/src/acp/server/custom_dispatch.rs index 9bc6627c1..ca8dbfc01 100644 --- a/crates/goose/src/acp/server/custom_dispatch.rs +++ b/crates/goose/src/acp/server/custom_dispatch.rs @@ -328,6 +328,30 @@ impl GooseAcpAgent { self.on_authenticate_provider_config(req).await } + #[custom_method(ProviderSecretsListRequest)] + async fn dispatch_list_provider_secrets( + &self, + req: ProviderSecretsListRequest, + ) -> Result { + self.on_list_provider_secrets(req).await + } + + #[custom_method(ProviderSecretDeleteRequest)] + async fn dispatch_delete_provider_secret( + &self, + req: ProviderSecretDeleteRequest, + ) -> Result { + self.on_delete_provider_secret(req).await + } + + #[custom_method(CanonicalModelInfoRequest)] + async fn dispatch_canonical_model_info( + &self, + req: CanonicalModelInfoRequest, + ) -> Result { + self.on_canonical_model_info(req).await + } + #[custom_method(PreferencesReadRequest)] async fn dispatch_preferences_read( &self, @@ -368,6 +392,14 @@ impl GooseAcpAgent { self.on_defaults_save(req).await } + #[custom_method(DefaultsClearRequest)] + async fn dispatch_defaults_clear( + &self, + req: DefaultsClearRequest, + ) -> Result { + self.on_defaults_clear(req).await + } + #[custom_method(OnboardingImportScanRequest)] async fn dispatch_onboarding_import_scan( &self, diff --git a/crates/goose/src/acp/server/providers.rs b/crates/goose/src/acp/server/providers.rs index 342d647d7..d1a496137 100644 --- a/crates/goose/src/acp/server/providers.rs +++ b/crates/goose/src/acp/server/providers.rs @@ -1,8 +1,39 @@ use super::*; use crate::config::declarative_providers; use crate::providers::inventory::ensure_refresh_identity_current; +use crate::providers::provider_secrets; use std::str::FromStr; +fn provider_secret_to_dto(secret: provider_secrets::ProviderSecret) -> ProviderSecretDto { + let storage = match secret.storage { + provider_secrets::ProviderSecretStorage::SecretStore => { + ProviderSecretStorageDto::SecretStore + } + provider_secrets::ProviderSecretStorage::ProviderCache => { + ProviderSecretStorageDto::ProviderCache + } + }; + let status = match secret.status { + provider_secrets::ProviderSecretStatus::Valid => ProviderSecretStatusDto::Valid, + provider_secrets::ProviderSecretStatus::Expired => ProviderSecretStatusDto::Expired, + provider_secrets::ProviderSecretStatus::Unknown => ProviderSecretStatusDto::Unknown, + }; + ProviderSecretDto { + id: secret.id, + provider: secret.provider, + provider_display_name: secret.provider_display_name, + name: secret.name, + storage, + expires_at: secret.expires_at.map(|value| value.to_rfc3339()), + status, + configured: secret.configured, + has_secret: secret.has_secret, + can_delete: secret.can_delete, + can_configure: secret.can_configure, + configure_provider: secret.configure_provider, + } +} + fn inventory_entry_to_dto(entry: ProviderInventoryEntry) -> ProviderInventoryEntryDto { let stale = ProviderInventoryService::is_stale(&entry); ProviderInventoryEntryDto { @@ -927,22 +958,29 @@ impl GooseAcpAgent { let entry = crate::providers::get_from_registry(&req.provider_id) .await .invalid_params_err_ctx("Unknown provider")?; - let metadata = entry.metadata().clone(); - if !metadata.config_keys.iter().any(|key| key.oauth_flow) { - return Err(agent_client_protocol::Error::invalid_params().data(format!( - "Provider does not support native authentication: {}", - req.provider_id - ))); - } - let provider = entry - .create_with_default_model(Vec::new()) - .await - .internal_err_ctx("Failed to initialize provider")?; - provider - .configure_oauth() - .await - .internal_err_ctx("Failed to authenticate provider")?; + if req.provider_id == crate::providers::huggingface_auth::HUGGINGFACE_PROVIDER_NAME { + crate::providers::huggingface_auth::configure_oauth() + .await + .internal_err_ctx("Failed to authenticate provider")?; + } else { + let metadata = entry.metadata().clone(); + if !metadata.config_keys.iter().any(|key| key.oauth_flow) { + return Err(agent_client_protocol::Error::invalid_params().data(format!( + "Provider does not support native authentication: {}", + req.provider_id + ))); + } + + let provider = entry + .create_with_default_model(Vec::new()) + .await + .internal_err_ctx("Failed to initialize provider")?; + provider + .configure_oauth() + .await + .internal_err_ctx("Failed to authenticate provider")?; + } Config::global().invalidate_secrets_cache(); let provider_ids = [req.provider_id.clone()]; @@ -950,4 +988,58 @@ impl GooseAcpAgent { let refresh = self.start_provider_inventory_refresh(&provider_ids).await?; Ok(ProviderConfigChangeResponse { status, refresh }) } + + pub(super) async fn on_list_provider_secrets( + &self, + _req: ProviderSecretsListRequest, + ) -> Result { + let secrets = provider_secrets::list_provider_secrets() + .await + .internal_err_ctx("Failed to list provider secrets")? + .into_iter() + .map(provider_secret_to_dto) + .collect(); + Ok(ProviderSecretsListResponse { secrets }) + } + + pub(super) async fn on_delete_provider_secret( + &self, + req: ProviderSecretDeleteRequest, + ) -> Result { + match provider_secrets::delete_provider_secret(&req.id).await { + Ok(()) => Ok(EmptyResponse {}), + Err(provider_secrets::DeleteProviderSecretError::InvalidId(id)) => { + Err(agent_client_protocol::Error::invalid_params() + .data(format!("Invalid provider secret id: '{}'", id))) + } + Err(e) => Err(agent_client_protocol::Error::internal_error().data(e.to_string())), + } + } + + pub(super) async fn on_canonical_model_info( + &self, + req: CanonicalModelInfoRequest, + ) -> Result { + use goose_providers::model::ModelConfig; + + let model_info = + crate::providers::canonical::maybe_get_canonical_model(&req.provider, &req.model).map( + |canonical_model| CanonicalModelInfoDto { + provider: req.provider.clone(), + model: req.model.clone(), + context_limit: canonical_model.limit.context, + max_output_tokens: canonical_model.limit.output, + reasoning: canonical_model + .reasoning + .unwrap_or_else(|| ModelConfig::new(&req.model).is_reasoning_model()), + input_token_cost: canonical_model.cost.input, + output_token_cost: canonical_model.cost.output, + cache_read_token_cost: canonical_model.cost.cache_read, + cache_write_token_cost: canonical_model.cost.cache_write, + currency: "$".to_string(), + }, + ); + + Ok(CanonicalModelInfoResponse { model_info }) + } } diff --git a/crates/goose/src/config/mod.rs b/crates/goose/src/config/mod.rs index 13abeec00..cfe7a814f 100644 --- a/crates/goose/src/config/mod.rs +++ b/crates/goose/src/config/mod.rs @@ -32,6 +32,6 @@ pub use extensions::DEFAULT_EXTENSION; pub use extensions::DEFAULT_EXTENSION_DESCRIPTION; pub use extensions::DEFAULT_EXTENSION_TIMEOUT; pub use providers::{ - get_active_model, get_active_provider, get_provider_entry, set_active_provider, - set_provider_entry, ProviderEntry, + clear_active_provider, get_active_model, get_active_provider, get_provider_entry, + set_active_provider, set_provider_entry, ProviderEntry, }; diff --git a/crates/goose/src/config/providers.rs b/crates/goose/src/config/providers.rs index fd7dd14fc..c8068bad2 100644 --- a/crates/goose/src/config/providers.rs +++ b/crates/goose/src/config/providers.rs @@ -8,11 +8,6 @@ use tracing::warn; const PROVIDERS_CONFIG_KEY: &str = "providers"; const ACTIVE_PROVIDER_KEY: &str = "active_provider"; -/// A single provider's persisted configuration within the `providers:` block. -/// -/// The `providers` block in config.yaml is the authoritative source for -/// per-provider settings, replacing the old flat-key scheme where switching -/// providers destructively overwrote `GOOSE_PROVIDER` / `GOOSE_MODEL`. #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ProviderEntry { #[serde(default)] @@ -23,10 +18,6 @@ pub struct ProviderEntry { pub configured: bool, } -// --------------------------------------------------------------------------- -// Read helpers -// --------------------------------------------------------------------------- - fn parse_providers_map(raw: Mapping) -> IndexMap { let mut map = IndexMap::with_capacity(raw.len()); for (k, v) in raw { @@ -53,16 +44,10 @@ fn get_providers_map(config: &Config) -> IndexMap { parse_providers_map(raw) } -/// Retrieve the [`ProviderEntry`] for a named provider, if it exists. pub fn get_provider_entry(config: &Config, name: &str) -> Option { get_providers_map(config).get(name).cloned() } -// --------------------------------------------------------------------------- -// Write helpers -// --------------------------------------------------------------------------- - -/// Persist a [`ProviderEntry`] under `providers.{name}`. pub fn set_provider_entry( config: &Config, name: &str, @@ -77,45 +62,20 @@ pub fn set_provider_entry( }) } -// --------------------------------------------------------------------------- -// Active-provider accessors -// --------------------------------------------------------------------------- - -/// Return the currently active provider name. -/// -/// Resolution order: -/// 1. `GOOSE_PROVIDER` environment variable (uppercase check performed by -/// `get_param`) -/// 2. `active_provider` key in config.yaml -/// 3. Legacy flat `GOOSE_PROVIDER` key in config.yaml (backward compat) pub fn get_active_provider(config: &Config) -> Option { - // Env var takes precedence (get_param checks env automatically) if let Ok(val) = env::var("GOOSE_PROVIDER") { return Some(val); } - - // New structured key if let Ok(val) = config.get_param::(ACTIVE_PROVIDER_KEY) { return Some(val); } - - // Legacy flat key fallback config.get_param::("GOOSE_PROVIDER").ok() } -/// Return the model for the currently active provider. -/// -/// Resolution order: -/// 1. `GOOSE_MODEL` environment variable -/// 2. Model recorded in the active provider's entry (`providers.{name}.model`) -/// 3. Legacy flat `GOOSE_MODEL` key in config.yaml pub fn get_active_model(config: &Config) -> Option { - // Env var takes precedence if let Ok(val) = env::var("GOOSE_MODEL") { return Some(val); } - - // Try provider entry model if let Some(provider_name) = get_active_provider(config) { if let Some(entry) = get_provider_entry(config, &provider_name) { if !entry.model.is_empty() { @@ -123,17 +83,9 @@ pub fn get_active_model(config: &Config) -> Option { } } } - - // Legacy flat key fallback config.get_param::("GOOSE_MODEL").ok() } -/// Set the active provider and update its entry in the `providers` block. -/// -/// This writes: -/// - `active_provider: {name}` at the top level -/// - `providers.{name}` with `configured: true`, `enabled: true`, and the -/// supplied model. pub fn set_active_provider(config: &Config, name: &str, model: &str) -> Result<(), ConfigError> { config.set_param(ACTIVE_PROVIDER_KEY, name)?; let entry = ProviderEntry { @@ -144,6 +96,16 @@ pub fn set_active_provider(config: &Config, name: &str, model: &str) -> Result<( set_provider_entry(config, name, &entry) } +pub fn clear_active_provider(config: &Config) -> Result<(), ConfigError> { + for key in [ACTIVE_PROVIDER_KEY, "GOOSE_PROVIDER", "GOOSE_MODEL"] { + match config.delete(key) { + Ok(()) | Err(ConfigError::NotFound(_)) => {} + Err(e) => return Err(e), + } + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -171,12 +133,6 @@ mod tests { assert!(loaded.configured); } - #[test] - fn test_get_provider_entry_missing() { - let config = new_test_config(); - assert!(get_provider_entry(&config, "nonexistent").is_none()); - } - #[test] fn test_set_active_provider_writes_structured_keys() { let config = new_test_config(); @@ -192,28 +148,28 @@ mod tests { } #[test] - fn test_get_active_provider_from_new_key() { + fn test_clear_active_provider_preserves_provider_entries() { let config = new_test_config(); - config.set_param(ACTIVE_PROVIDER_KEY, "openai").unwrap(); + set_active_provider(&config, "openai", "gpt-4o").unwrap(); - let result = get_active_provider(&config); - assert_eq!(result, Some("openai".to_string())); + clear_active_provider(&config).unwrap(); + + assert!(get_active_provider(&config).is_none()); + let entry = get_provider_entry(&config, "openai").unwrap(); + assert_eq!(entry.model, "gpt-4o"); + assert!(entry.configured); } #[test] - fn test_get_active_provider_falls_back_to_legacy() { + fn test_clear_active_provider_removes_legacy_keys() { let config = new_test_config(); config.set_param("GOOSE_PROVIDER", "anthropic").unwrap(); + config.set_param("GOOSE_MODEL", "claude").unwrap(); - let result = get_active_provider(&config); - assert_eq!(result, Some("anthropic".to_string())); - } + clear_active_provider(&config).unwrap(); - #[test] - fn test_get_active_provider_none_when_empty() { - let config = new_test_config(); - let result = get_active_provider(&config); - assert_eq!(result, None); + assert!(get_active_provider(&config).is_none()); + assert!(get_active_model(&config).is_none()); } #[test] @@ -225,25 +181,12 @@ mod tests { assert_eq!(result, Some("gpt-4o".to_string())); } - #[test] - fn test_get_active_model_falls_back_to_legacy() { - let config = new_test_config(); - // Only set the legacy key, no providers block - config.set_param("GOOSE_MODEL", "gpt-3.5-turbo").unwrap(); - - let result = get_active_model(&config); - assert_eq!(result, Some("gpt-3.5-turbo".to_string())); - } - #[test] fn test_multiple_providers_preserved() { let config = new_test_config(); - - // Set up two providers set_active_provider(&config, "openai", "gpt-4o").unwrap(); set_active_provider(&config, "anthropic", "claude-3-opus").unwrap(); - // Both entries should exist let openai = get_provider_entry(&config, "openai").unwrap(); assert_eq!(openai.model, "gpt-4o"); assert!(openai.configured); @@ -252,8 +195,6 @@ mod tests { assert_eq!(anthropic.model, "claude-3-opus"); assert!(anthropic.configured); - // Active provider should be the last one set - let active = get_active_provider(&config); - assert_eq!(active, Some("anthropic".to_string())); + assert_eq!(get_active_provider(&config), Some("anthropic".to_string())); } } diff --git a/crates/goose/src/providers/mod.rs b/crates/goose/src/providers/mod.rs index 06ba551ed..57a0c59ca 100644 --- a/crates/goose/src/providers/mod.rs +++ b/crates/goose/src/providers/mod.rs @@ -62,6 +62,7 @@ pub mod openai_compatible { pub mod openrouter; pub mod pi_acp; pub mod provider_registry; +pub mod provider_secrets; pub mod provider_test; mod retry { pub use goose_providers::retry::*; diff --git a/crates/goose/src/providers/provider_secrets.rs b/crates/goose/src/providers/provider_secrets.rs new file mode 100644 index 000000000..97859b461 --- /dev/null +++ b/crates/goose/src/providers/provider_secrets.rs @@ -0,0 +1,651 @@ +use std::collections::{HashMap, HashSet}; +use std::path::Path; + +use chrono::{DateTime, TimeZone, Utc}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use utoipa::ToSchema; + +use crate::config::paths::Paths; +use crate::config::{Config, ConfigError}; +use crate::providers::base::{ProviderMetadata, ProviderType}; +use crate::providers::huggingface_auth; + +pub const SECRET_STORE_ID_PREFIX: &str = "secret_store:"; +pub const PROVIDER_CACHE_ID_PREFIX: &str = "provider_cache:"; + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ProviderSecretStorage { + SecretStore, + ProviderCache, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ProviderSecretStatus { + Valid, + Expired, + Unknown, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct ProviderSecret { + pub id: String, + pub provider: String, + pub provider_display_name: String, + pub name: String, + pub storage: ProviderSecretStorage, + pub expires_at: Option>, + pub status: ProviderSecretStatus, + pub configured: bool, + pub has_secret: bool, + pub can_delete: bool, + pub can_configure: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub configure_provider: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum DeleteProviderSecretError { + #[error("Invalid provider secret id: '{0}'")] + InvalidId(String), + #[error(transparent)] + Config(#[from] ConfigError), + #[error(transparent)] + Other(#[from] anyhow::Error), +} + +fn provider_secret_status(expires_at: Option>) -> ProviderSecretStatus { + match expires_at { + Some(expires_at) if expires_at <= Utc::now() => ProviderSecretStatus::Expired, + Some(_) => ProviderSecretStatus::Valid, + None => ProviderSecretStatus::Unknown, + } +} + +fn parse_expiry_value(value: &Value) -> Option> { + match value { + Value::String(value) => DateTime::parse_from_rfc3339(value) + .ok() + .map(|dt| dt.with_timezone(&Utc)), + Value::Number(value) => value + .as_i64() + .and_then(|timestamp| Utc.timestamp_opt(timestamp, 0).single()), + _ => None, + } +} + +fn find_expires_at(value: &Value) -> Option> { + match value { + Value::Object(map) => { + if map + .get("refresh_token") + .and_then(Value::as_str) + .is_some_and(|token| !token.is_empty()) + { + return None; + } + if let Some(expires_at) = map.get("expires_at").and_then(parse_expiry_value) { + return Some(expires_at); + } + if let Some(expires_at) = map.get("expires_on").and_then(parse_expiry_value) { + return Some(expires_at); + } + map.values().find_map(find_expires_at) + } + Value::Array(values) => values.iter().find_map(find_expires_at), + _ => None, + } +} + +#[derive(Clone, Copy)] +struct ProviderCacheSecretDefinition { + provider: &'static str, + name: &'static str, + path: &'static str, + is_directory: bool, +} + +const PROVIDER_CACHE_SECRET_DEFINITIONS: &[ProviderCacheSecretDefinition] = &[ + ProviderCacheSecretDefinition { + provider: "gemini_oauth", + name: "OAuth token", + path: "gemini_oauth/tokens.json", + is_directory: false, + }, + ProviderCacheSecretDefinition { + provider: "chatgpt_codex", + name: "OAuth token", + path: "chatgpt_codex/tokens.json", + is_directory: false, + }, + ProviderCacheSecretDefinition { + provider: "kimi_code", + name: "OAuth token", + path: "kimicode/token.json", + is_directory: false, + }, + ProviderCacheSecretDefinition { + provider: "github_copilot", + name: "OAuth token", + path: "githubcopilot", + is_directory: true, + }, + ProviderCacheSecretDefinition { + provider: "xai_oauth", + name: "OAuth token", + path: "xai_oauth/tokens.json", + is_directory: false, + }, + ProviderCacheSecretDefinition { + provider: "databricks", + name: "OAuth token", + path: "databricks/oauth", + is_directory: true, + }, + ProviderCacheSecretDefinition { + provider: "databricks_v2", + name: "OAuth token", + path: "databricks/oauth", + is_directory: true, + }, +]; + +fn provider_cache_definitions_for_display() -> Vec { + let mut seen_paths = HashSet::new(); + PROVIDER_CACHE_SECRET_DEFINITIONS + .iter() + .copied() + .filter(|definition| seen_paths.insert(definition.path)) + .collect() +} + +fn provider_cache_definition(provider: &str) -> Option { + PROVIDER_CACHE_SECRET_DEFINITIONS + .iter() + .copied() + .find(|definition| definition.provider == provider) +} + +fn provider_cache_providers_sharing_cache(provider: &str) -> Vec<&'static str> { + let Some(definition) = provider_cache_definition(provider) else { + return Vec::new(); + }; + + PROVIDER_CACHE_SECRET_DEFINITIONS + .iter() + .filter(|other| other.path == definition.path) + .map(|definition| definition.provider) + .collect() +} + +fn read_json_file(path: &Path) -> Option { + std::fs::read_to_string(path) + .ok() + .and_then(|contents| serde_json::from_str(&contents).ok()) +} + +fn collect_json_expiries(path: &Path, is_directory: bool) -> Vec> { + if !is_directory { + return read_json_file(path) + .and_then(|value| find_expires_at(&value)) + .into_iter() + .collect(); + } + + let mut expiries = Vec::new(); + let mut stack = vec![path.to_path_buf()]; + + while let Some(current) = stack.pop() { + let Ok(entries) = std::fs::read_dir(current) else { + continue; + }; + + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + stack.push(path); + continue; + } + if path.extension().and_then(|ext| ext.to_str()) != Some("json") { + continue; + } + if let Some(expires_at) = + read_json_file(&path).and_then(|value| find_expires_at(&value)) + { + expiries.push(expires_at); + } + } + } + + expiries +} + +fn provider_cache_exists(path: &Path, is_directory: bool) -> bool { + if !is_directory { + return path.is_file(); + } + + let Ok(entries) = std::fs::read_dir(path) else { + return false; + }; + + entries.flatten().any(|entry| { + let path = entry.path(); + path.is_file() || provider_cache_exists(&path, true) + }) +} + +fn provider_cache_expiry(definition: ProviderCacheSecretDefinition) -> Option> { + let path = Paths::in_config_dir(definition.path); + collect_json_expiries(&path, definition.is_directory) + .into_iter() + .min() +} + +fn build_provider_cache_secret( + definition: ProviderCacheSecretDefinition, + display_names: &HashMap, +) -> Option { + let path = Paths::in_config_dir(definition.path); + if !provider_cache_exists(&path, definition.is_directory) { + return None; + } + + let expires_at = provider_cache_expiry(definition); + Some(ProviderSecret { + id: format!("{}{}", PROVIDER_CACHE_ID_PREFIX, definition.provider), + provider: definition.provider.to_string(), + provider_display_name: display_names + .get(definition.provider) + .cloned() + .unwrap_or_else(|| definition.provider.to_string()), + name: definition.name.to_string(), + storage: ProviderSecretStorage::ProviderCache, + expires_at, + status: provider_secret_status(expires_at), + configured: true, + has_secret: true, + can_delete: true, + can_configure: false, + configure_provider: None, + }) +} + +fn build_huggingface_oauth_secret( + token: Option, +) -> ProviderSecret { + let expires_at = token.as_ref().and_then(|token| token.expires_at); + let has_secret = token.is_some(); + + ProviderSecret { + id: format!( + "{}{}", + PROVIDER_CACHE_ID_PREFIX, + huggingface_auth::HUGGINGFACE_PROVIDER_NAME + ), + provider: huggingface_auth::HUGGINGFACE_PROVIDER_NAME.to_string(), + provider_display_name: huggingface_auth::HUGGINGFACE_DISPLAY_NAME.to_string(), + name: huggingface_auth::HUGGINGFACE_OAUTH_TOKEN_NAME.to_string(), + storage: ProviderSecretStorage::ProviderCache, + expires_at, + status: provider_secret_status(expires_at), + configured: has_secret, + has_secret, + can_delete: has_secret, + can_configure: true, + configure_provider: Some(huggingface_auth::HUGGINGFACE_PROVIDER_NAME.to_string()), + } +} + +fn build_secret_store_secrets( + stored_secrets: &HashMap, + providers: &[(ProviderMetadata, ProviderType)], +) -> Vec { + let mut secrets = Vec::new(); + + for (metadata, _) in providers { + for config_key in metadata.config_keys.iter().filter(|key| key.secret) { + if !stored_secrets.contains_key(&config_key.name) { + continue; + } + secrets.push(ProviderSecret { + id: format!( + "{}{}:{}", + SECRET_STORE_ID_PREFIX, metadata.name, config_key.name + ), + provider: metadata.name.clone(), + provider_display_name: metadata.display_name.clone(), + name: config_key.name.clone(), + storage: ProviderSecretStorage::SecretStore, + expires_at: None, + status: ProviderSecretStatus::Unknown, + configured: true, + has_secret: true, + can_delete: true, + can_configure: false, + configure_provider: None, + }); + } + } + + secrets +} + +fn is_known_provider_secret( + providers: &[(ProviderMetadata, ProviderType)], + provider: &str, + key: &str, +) -> bool { + providers + .iter() + .filter(|(metadata, _)| metadata.name == provider) + .flat_map(|(metadata, _)| metadata.config_keys.iter()) + .any(|config_key| config_key.secret && config_key.name == key) +} + +fn unconfigure_provider(config: &Config, provider_name: &str) -> Result<(), ConfigError> { + if let Some(mut entry) = crate::config::get_provider_entry(config, provider_name) { + entry.configured = false; + crate::config::set_provider_entry(config, provider_name, &entry)?; + } + + let configured_marker = format!("{}_configured", provider_name); + match config.delete(&configured_marker) { + Ok(()) | Err(ConfigError::NotFound(_)) => Ok(()), + Err(e) => Err(e), + } +} + +fn parse_secret_store_id(id: &str) -> Option<(&str, &str)> { + let rest = id.strip_prefix(SECRET_STORE_ID_PREFIX)?; + rest.split_once(':') +} + +fn parse_provider_cache_id(id: &str) -> Option<&str> { + id.strip_prefix(PROVIDER_CACHE_ID_PREFIX) +} + +fn is_valid_provider_name(provider_name: &str) -> bool { + !provider_name.is_empty() + && provider_name + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') +} + +fn should_unconfigure_after_secret_delete( + provider: &str, + key: &str, + has_usable_huggingface_oauth_token: impl FnOnce() -> bool, +) -> bool { + provider == huggingface_auth::HUGGINGFACE_PROVIDER_NAME + && key == huggingface_auth::HUGGINGFACE_TOKEN_SECRET_KEY + && !has_usable_huggingface_oauth_token() +} + +pub async fn list_provider_secrets() -> Result, ConfigError> { + let config = Config::global(); + let stored_secrets = config.all_secrets()?; + let providers = crate::providers::providers().await; + let display_names: HashMap = providers + .iter() + .map(|(metadata, _)| (metadata.name.clone(), metadata.display_name.clone())) + .collect(); + + let mut secrets = build_secret_store_secrets(&stored_secrets, &providers); + + for definition in provider_cache_definitions_for_display() { + if let Some(secret) = build_provider_cache_secret(definition, &display_names) { + if !secrets.iter().any(|existing| existing.id == secret.id) { + secrets.push(secret); + } + } + } + + let huggingface_secret = build_huggingface_oauth_secret(huggingface_auth::load_oauth_token()); + if let Some(existing) = secrets + .iter_mut() + .find(|existing| existing.id == huggingface_secret.id) + { + *existing = huggingface_secret; + } else { + secrets.push(huggingface_secret); + } + + secrets.sort_by(|a, b| { + a.provider_display_name + .cmp(&b.provider_display_name) + .then_with(|| a.name.cmp(&b.name)) + }); + + Ok(secrets) +} + +pub async fn delete_provider_secret(id: &str) -> Result<(), DeleteProviderSecretError> { + let config = Config::global(); + + if let Some((provider, key)) = parse_secret_store_id(id) { + let providers = crate::providers::providers().await; + if !is_known_provider_secret(&providers, provider, key) { + return Err(DeleteProviderSecretError::InvalidId(id.to_string())); + } + + config.delete_secret(key)?; + if should_unconfigure_after_secret_delete(provider, key, || { + huggingface_auth::has_configured_token().unwrap_or(false) + }) { + unconfigure_provider(config, provider)?; + } + return Ok(()); + } + + if let Some(provider) = parse_provider_cache_id(id) { + if provider == huggingface_auth::HUGGINGFACE_PROVIDER_NAME { + huggingface_auth::clear_oauth_token()?; + unconfigure_provider(config, provider)?; + return Ok(()); + } + + if !is_valid_provider_name(provider) || provider_cache_definition(provider).is_none() { + return Err(DeleteProviderSecretError::InvalidId(id.to_string())); + } + crate::providers::cleanup_provider(provider).await?; + for shared_provider in provider_cache_providers_sharing_cache(provider) { + unconfigure_provider(config, shared_provider)?; + } + return Ok(()); + } + + Err(DeleteProviderSecretError::InvalidId(id.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::ProviderEntry; + use crate::providers::base::ConfigKey; + use serde_json::json; + + fn new_test_config() -> Config { + let unique = format!( + "goose-provider-secrets-test-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + let config_path = std::env::temp_dir().join(format!("{unique}-config.yaml")); + let secrets_path = std::env::temp_dir().join(format!("{unique}-secrets.yaml")); + Config::new_with_file_secrets(config_path, secrets_path).unwrap() + } + + #[test] + fn secret_store_listing_only_includes_provider_secret_keys() { + let metadata = ProviderMetadata::new( + "openai", + "OpenAI", + "OpenAI provider", + "gpt-4o", + vec![], + "https://example.com", + vec![ + ConfigKey::new("OPENAI_API_KEY", true, true, None, true), + ConfigKey::new("OPENAI_HOST", false, false, None, false), + ], + ); + let providers = vec![(metadata, ProviderType::Builtin)]; + let stored_secrets = HashMap::from([ + ( + "OPENAI_API_KEY".to_string(), + Value::String("secret-value".to_string()), + ), + ( + "UNRELATED_SECRET".to_string(), + Value::String("other-secret".to_string()), + ), + ( + "OPENAI_HOST".to_string(), + Value::String("https://api.openai.com".to_string()), + ), + ]); + + let secrets = build_secret_store_secrets(&stored_secrets, &providers); + + assert_eq!(secrets.len(), 1); + assert_eq!(secrets[0].id, "secret_store:openai:OPENAI_API_KEY"); + assert_eq!(secrets[0].provider_display_name, "OpenAI"); + assert_eq!(secrets[0].name, "OPENAI_API_KEY"); + assert_eq!(secrets[0].storage, ProviderSecretStorage::SecretStore); + assert_eq!(secrets[0].status, ProviderSecretStatus::Unknown); + } + + #[test] + fn provider_secret_delete_validation_requires_provider_secret_key() { + let metadata = ProviderMetadata::new( + "openai", + "OpenAI", + "OpenAI provider", + "gpt-4o", + vec![], + "https://example.com", + vec![ + ConfigKey::new("OPENAI_API_KEY", true, true, None, true), + ConfigKey::new("OPENAI_HOST", false, false, None, false), + ], + ); + let providers = vec![(metadata, ProviderType::Builtin)]; + + assert!(is_known_provider_secret( + &providers, + "openai", + "OPENAI_API_KEY" + )); + assert!(!is_known_provider_secret( + &providers, + "openai", + "OPENAI_HOST" + )); + assert!(!is_known_provider_secret( + &providers, + "openai", + "UNRELATED_SECRET" + )); + assert!(!is_known_provider_secret( + &providers, + "anthropic", + "OPENAI_API_KEY" + )); + } + + #[test] + fn expiry_extraction_handles_nested_rfc3339_values() { + let expires_at = Utc::now() + chrono::Duration::hours(1); + let value = json!({ + "project_id": "project", + "token": { + "access_token": "secret", + "expires_at": expires_at.to_rfc3339(), + } + }); + + let parsed = find_expires_at(&value).expect("expected expiry"); + + assert_eq!(parsed.timestamp(), expires_at.timestamp()); + assert_eq!( + provider_secret_status(Some(parsed)), + ProviderSecretStatus::Valid + ); + } + + #[test] + fn expiry_extraction_ignores_refreshable_access_tokens() { + let expires_at = Utc::now() - chrono::Duration::hours(1); + let value = json!({ + "access_token": "access", + "refresh_token": "refresh", + "expires_at": expires_at.to_rfc3339(), + }); + + assert_eq!(find_expires_at(&value), None); + } + + #[test] + fn expiry_extraction_handles_expired_unix_timestamps() { + let value = json!({ + "info": { + "expires_at": 1 + } + }); + + let parsed = find_expires_at(&value).expect("expected expiry"); + + assert_eq!(parsed.timestamp(), 1); + assert_eq!( + provider_secret_status(Some(parsed)), + ProviderSecretStatus::Expired + ); + } + + #[test] + fn unconfigure_provider_clears_structured_entry() { + let config = new_test_config(); + crate::config::set_provider_entry( + &config, + "huggingface", + &ProviderEntry { + enabled: true, + model: "Qwen/Qwen3-Coder-480B-A35B-Instruct".to_string(), + configured: true, + }, + ) + .unwrap(); + + unconfigure_provider(&config, "huggingface").unwrap(); + + let entry = crate::config::get_provider_entry(&config, "huggingface").unwrap(); + assert!(entry.enabled); + assert_eq!(entry.model, "Qwen/Qwen3-Coder-480B-A35B-Instruct"); + assert!(!entry.configured); + } + + #[test] + fn unconfigure_provider_deletes_legacy_configured_marker() { + let config = new_test_config(); + config.set_param("huggingface_configured", true).unwrap(); + + unconfigure_provider(&config, "huggingface").unwrap(); + + assert!(config.get_param::("huggingface_configured").is_err()); + } + + #[test] + fn deleting_huggingface_token_unconfigures_without_oauth() { + assert!(should_unconfigure_after_secret_delete( + "huggingface", + "HF_TOKEN", + || false + )); + } +} diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 87a53686c..d2229df0e 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -1207,33 +1207,6 @@ } } }, - "/config/providers/{name}/oauth": { - "post": { - "tags": [ - "super::routes::config_management" - ], - "operationId": "configure_provider_oauth", - "parameters": [ - { - "name": "name", - "in": "path", - "description": "Provider name", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OAuth configuration completed" - }, - "400": { - "description": "OAuth configuration failed" - } - } - } - }, "/config/read": { "post": { "tags": [ diff --git a/ui/desktop/src/App.test.tsx b/ui/desktop/src/App.test.tsx index 8fb079adc..08e795cb4 100644 --- a/ui/desktop/src/App.test.tsx +++ b/ui/desktop/src/App.test.tsx @@ -63,6 +63,15 @@ vi.mock('./sessions', () => ({ createSession: vi.fn(), })); +// Mock the ACP providers module used by OnboardingGuard so it doesn't try to +// open a real ACP client connection during tests. Returning null defaults +// keeps the app in the "brand new" (no provider configured) onboarding state. +vi.mock('./acp/providers', () => ({ + acpReadDefaults: vi.fn().mockResolvedValue({ providerId: null, modelId: null }), + acpSaveDefaults: vi.fn().mockResolvedValue(undefined), + acpListProviderDetails: vi.fn().mockResolvedValue([]), +})); + // Mock the ConfigContext module vi.mock('./components/ConfigContext', () => ({ useConfig: () => ({ diff --git a/ui/desktop/src/acp/providers.ts b/ui/desktop/src/acp/providers.ts new file mode 100644 index 000000000..933559a17 --- /dev/null +++ b/ui/desktop/src/acp/providers.ts @@ -0,0 +1,217 @@ +import type { + CanonicalModelInfoDto, + CustomProviderCreateRequest_unstable, + CustomProviderReadResponse_unstable, + ProviderSecretDto, + ProviderTemplateCatalogEntryDto, + ProviderTemplateDto, +} from '@aaif/goose-sdk'; +import type { ProviderDetails, ThinkingEffort, UpdateCustomProviderRequest } from '../api'; +import { getAcpClient } from './acpConnection'; + +export type { CanonicalModelInfoDto, ProviderSecretDto }; + +function updateRequestToCreate( + request: UpdateCustomProviderRequest +): CustomProviderCreateRequest_unstable { + return { + engine: request.engine, + displayName: request.display_name, + apiUrl: request.api_url, + apiKey: request.api_key || null, + models: request.models, + supportsStreaming: request.supports_streaming ?? null, + headers: request.headers ?? undefined, + requiresAuth: request.requires_auth ?? true, + catalogProviderId: request.catalog_provider_id ?? null, + basePath: request.base_path ?? null, + preservesThinking: request.preserves_thinking ?? null, + }; +} + +export async function acpListProviderDetails(): Promise { + const client = await getAcpClient(); + const { entries } = await client.goose.providersList_unstable({}); + return entries.map((entry) => ({ + name: entry.providerId, + is_configured: entry.configured, + provider_type: entry.providerType as ProviderDetails['provider_type'], + metadata: { + name: entry.providerId, + display_name: entry.providerName, + description: entry.description, + default_model: entry.defaultModel, + model_doc_link: '', + model_selection_hint: entry.modelSelectionHint ?? null, + config_keys: entry.configKeys.map((key) => ({ + name: key.name, + required: key.required, + secret: key.secret, + default: key.default ?? null, + oauth_flow: key.oauthFlow ?? false, + device_code_flow: key.deviceCodeFlow ?? false, + primary: key.primary ?? false, + })), + known_models: entry.models.map((model) => ({ + name: model.id, + context_limit: model.contextLimit ?? 0, + reasoning: model.reasoning ?? undefined, + })), + setup_steps: entry.setupSteps, + }, + })); +} + +export async function acpListProviderModels(providerId: string) { + const client = await getAcpClient(); + const { entries } = await client.goose.providersList_unstable({ providerIds: [providerId] }); + return entries.find((e) => e.providerId === providerId)?.models ?? []; +} + +export async function acpListProviderCatalogEntries( + format?: string +): Promise { + const client = await getAcpClient(); + const { providers } = await client.goose.providersCatalogList_unstable(format ? { format } : {}); + return providers; +} + +export async function acpGetProviderTemplate(providerId: string): Promise { + const client = await getAcpClient(); + const { template } = await client.goose.providersCatalogTemplate_unstable({ providerId }); + return template; +} + +export async function acpGetCustomProvider( + providerId: string +): Promise { + const client = await getAcpClient(); + return client.goose.providersCustomRead_unstable({ providerId }); +} + +export async function acpCreateCustomProviderFromRequest( + request: UpdateCustomProviderRequest +): Promise<{ provider_name: string }> { + const client = await getAcpClient(); + const response = await client.goose.providersCustomCreate_unstable( + updateRequestToCreate(request) + ); + return { provider_name: response.providerId }; +} + +export async function acpUpdateCustomProviderFromRequest( + providerId: string, + request: UpdateCustomProviderRequest +): Promise { + const client = await getAcpClient(); + await client.goose.providersCustomUpdate_unstable({ + providerId, + ...updateRequestToCreate(request), + }); +} + +export async function acpDeleteCustomProvider(providerId: string): Promise { + const client = await getAcpClient(); + await client.goose.providersCustomDelete_unstable({ providerId }); +} + +export async function acpReadProviderConfig(providerId: string) { + const client = await getAcpClient(); + const { fields } = await client.goose.providersConfigRead_unstable({ providerId }); + return fields; +} + +export async function acpDeleteProviderConfig(providerId: string): Promise { + const client = await getAcpClient(); + await client.goose.providersConfigDelete_unstable({ providerId }); +} + +export async function acpSaveProviderConfig( + providerId: string, + fields: { key: string; value: string }[] +): Promise { + const client = await getAcpClient(); + await client.goose.providersConfigSave_unstable({ providerId, fields }); +} + +export async function acpAuthenticateProvider(providerId: string): Promise { + const client = await getAcpClient(); + await client.goose.providersConfigAuthenticate_unstable({ providerId }); +} + +export async function acpListProviderSecrets(): Promise { + const client = await getAcpClient(); + const { secrets } = await client.goose.providersSecretsList_unstable({}); + return secrets; +} + +export async function acpDeleteProviderSecret(id: string): Promise { + const client = await getAcpClient(); + await client.goose.providersSecretsDelete_unstable({ id }); +} + +export async function acpGetCanonicalModelInfo( + provider: string, + model: string +): Promise { + const client = await getAcpClient(); + const { modelInfo } = await client.goose.providersCanonicalModelInfo_unstable({ + provider, + model, + }); + return modelInfo ?? null; +} + +export async function acpReadDefaults(): Promise<{ + providerId: string | null; + modelId: string | null; +}> { + const client = await getAcpClient(); + const response = await client.goose.defaultsRead_unstable({}); + return { + providerId: response.providerId ?? null, + modelId: response.modelId ?? null, + }; +} + +export async function acpSaveDefaults(providerId: string, modelId?: string | null): Promise { + const client = await getAcpClient(); + await client.goose.defaultsSave_unstable({ providerId, modelId: modelId ?? null }); +} + +export async function acpClearDefaults(): Promise { + const client = await getAcpClient(); + await client.goose.defaultsClear_unstable({}); +} + +export async function acpReadThinkingEffort(): Promise { + const client = await getAcpClient(); + const response = await client.goose.preferencesRead_unstable({ keys: ['gooseThinkingEffort'] }); + const value = response.values.find((v) => v.key === 'gooseThinkingEffort')?.value; + return typeof value === 'string' ? (value as ThinkingEffort) : null; +} + +export async function acpSaveThinkingEffort(effort: ThinkingEffort): Promise { + const client = await getAcpClient(); + await client.goose.preferencesSave_unstable({ + values: [{ key: 'gooseThinkingEffort', value: effort }], + }); +} + +/** + * Switch the provider (and model) for an active session via ACP config options. + * + * Changing the provider on the server resets the session's model, so the model + * is applied as a follow-up step when supplied. + */ +export async function acpSetSessionProviderModel( + sessionId: string, + providerId: string, + modelId?: string | null +): Promise { + const client = await getAcpClient(); + await client.setSessionConfigOption({ sessionId, configId: 'provider', value: providerId }); + if (modelId) { + await client.setSessionConfigOption({ sessionId, configId: 'model', value: modelId }); + } +} diff --git a/ui/desktop/src/api/index.ts b/ui/desktop/src/api/index.ts index d143c8b38..412a7ad91 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, cancelDownload, cancelLocalModelDownload, checkProvider, cleanupProviderCache, configureProviderOauth, confirmToolAction, createCustomProvider, createSchedule, decodeRecipe, deleteLocalModel, deleteModel, deleteProviderSecret, deleteRecipe, deleteSchedule, diagnostics, downloadHfModel, downloadModel, encodeRecipe, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getLocalModelDownloadProgress, getModelSettings, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModelInfo, getProviderModels, getRepoFiles, getSession, getSessionExtensions, getSlashCommands, getTools, inspectRunningJob, killRunningJob, listBuiltinChatTemplates, listLocalModels, listModels, listProviderSecrets, listRecipes, listSchedules, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, recipeToYaml, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchHfModels, sendTelemetryEvent, sessionCancel, sessionEvents, sessionReply, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, startNanogptSetup, startOpenrouterSetup, startTetrateSetup, status, stopAgent, syncFeaturedModels, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateModelSettings, updateSchedule, updateSession, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, validateConfig } from './sdk.gen'; -export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CancelRequest, ChatRequest, ChatTemplate, CheckProviderData, CheckProviderRequest, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponse, CleanupProviderCacheResponses, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponse2, CreateCustomProviderResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteProviderSecretData, DeleteProviderSecretErrors, DeleteProviderSecretResponse, DeleteProviderSecretResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DiagnosticsConfig, DiagnosticsData, DiagnosticsError, DiagnosticsErrors, DiagnosticsExtensions, DiagnosticsLevel, DiagnosticsLogs, DiagnosticsPrompt, DiagnosticsReport, DiagnosticsResponse, DiagnosticsResponses, DiagnosticsScheduledRecipe, DiagnosticsTextFile, DictationProvider, DictationProviderStatus, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponse, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelRequest, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, 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, GetProviderModelInfoData, GetProviderModelInfoErrors, GetProviderModelInfoResponse, GetProviderModelInfoResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponse, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GooseApp, GooseMode, HfGgufFile, HfModelInfo, HfModelVariant, HfQuantVariant, Icon, IconTheme, ImageContent, InferenceMetadata, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListBuiltinChatTemplatesData, ListBuiltinChatTemplatesResponse, ListBuiltinChatTemplatesResponses, ListLocalModelsData, ListLocalModelsResponse, ListLocalModelsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListProviderSecretsData, ListProviderSecretsErrors, ListProviderSecretsResponse, ListProviderSecretsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, 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, ProviderModelInfoQuery, ProvidersData, ProviderSecret, ProviderSecretsResponse, ProviderSecretStatus, ProviderSecretStorage, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, 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, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionCancelData, SessionCancelResponses, SessionDisplayInfo, SessionEventsData, SessionEventsErrors, SessionEventsResponse, SessionEventsResponses, SessionExtensionsResponse, SessionReplyData, SessionReplyErrors, SessionReplyRequest, SessionReplyResponse, SessionReplyResponse2, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponse, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, SubRecipe, SuccessCheck, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, ThinkingEffort, TokenState, Tool, ToolAnnotations, ToolCallingMode, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, 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, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionRequest, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, Usage, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; +export { addExtension, agentAddExtension, agentRemoveExtension, cancelDownload, cancelLocalModelDownload, checkProvider, cleanupProviderCache, confirmToolAction, createCustomProvider, createSchedule, decodeRecipe, deleteLocalModel, deleteModel, deleteProviderSecret, deleteRecipe, deleteSchedule, diagnostics, downloadHfModel, downloadModel, encodeRecipe, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getLocalModelDownloadProgress, getModelSettings, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModelInfo, getProviderModels, getRepoFiles, getSession, getSessionExtensions, getSlashCommands, getTools, inspectRunningJob, killRunningJob, listBuiltinChatTemplates, listLocalModels, listModels, listProviderSecrets, listRecipes, listSchedules, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, recipeToYaml, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchHfModels, sendTelemetryEvent, sessionCancel, sessionEvents, sessionReply, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, startNanogptSetup, startOpenrouterSetup, startTetrateSetup, status, stopAgent, syncFeaturedModels, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateModelSettings, updateSchedule, updateSession, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, validateConfig } from './sdk.gen'; +export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CancelRequest, ChatRequest, ChatTemplate, CheckProviderData, CheckProviderRequest, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponse, CleanupProviderCacheResponses, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponse2, CreateCustomProviderResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteProviderSecretData, DeleteProviderSecretErrors, DeleteProviderSecretResponse, DeleteProviderSecretResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DiagnosticsConfig, DiagnosticsData, DiagnosticsError, DiagnosticsErrors, DiagnosticsExtensions, DiagnosticsLevel, DiagnosticsLogs, DiagnosticsPrompt, DiagnosticsReport, DiagnosticsResponse, DiagnosticsResponses, DiagnosticsScheduledRecipe, DiagnosticsTextFile, DictationProvider, DictationProviderStatus, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponse, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelRequest, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, 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, GetProviderModelInfoData, GetProviderModelInfoErrors, GetProviderModelInfoResponse, GetProviderModelInfoResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponse, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GooseApp, GooseMode, HfGgufFile, HfModelInfo, HfModelVariant, HfQuantVariant, Icon, IconTheme, ImageContent, InferenceMetadata, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListBuiltinChatTemplatesData, ListBuiltinChatTemplatesResponse, ListBuiltinChatTemplatesResponses, ListLocalModelsData, ListLocalModelsResponse, ListLocalModelsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListProviderSecretsData, ListProviderSecretsErrors, ListProviderSecretsResponse, ListProviderSecretsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, 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, ProviderModelInfoQuery, ProvidersData, ProviderSecret, ProviderSecretsResponse, ProviderSecretStatus, ProviderSecretStorage, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, 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, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionCancelData, SessionCancelResponses, SessionDisplayInfo, SessionEventsData, SessionEventsErrors, SessionEventsResponse, SessionEventsResponses, SessionExtensionsResponse, SessionReplyData, SessionReplyErrors, SessionReplyRequest, SessionReplyResponse, SessionReplyResponse2, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponse, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, SubRecipe, SuccessCheck, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, ThinkingEffort, TokenState, Tool, ToolAnnotations, ToolCallingMode, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, 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, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionRequest, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, Usage, 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 1bac488ae..34d0cb896 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, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CheckProviderData, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponses, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteProviderSecretData, DeleteProviderSecretErrors, DeleteProviderSecretResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, 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, GetProviderModelInfoData, GetProviderModelInfoErrors, GetProviderModelInfoResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListBuiltinChatTemplatesData, ListBuiltinChatTemplatesResponses, ListLocalModelsData, ListLocalModelsResponses, ListModelsData, ListModelsResponses, ListProviderSecretsData, ListProviderSecretsErrors, ListProviderSecretsResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, 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, SendTelemetryEventData, SendTelemetryEventResponses, SessionCancelData, SessionCancelResponses, SessionEventsData, SessionEventsErrors, SessionEventsResponses, SessionReplyData, SessionReplyErrors, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; +import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CheckProviderData, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteProviderSecretData, DeleteProviderSecretErrors, DeleteProviderSecretResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, 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, GetProviderModelInfoData, GetProviderModelInfoErrors, GetProviderModelInfoResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListBuiltinChatTemplatesData, ListBuiltinChatTemplatesResponses, ListLocalModelsData, ListLocalModelsResponses, ListModelsData, ListModelsResponses, ListProviderSecretsData, ListProviderSecretsErrors, ListProviderSecretsResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, 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, SendTelemetryEventData, SendTelemetryEventResponses, SessionCancelData, SessionCancelResponses, SessionEventsData, SessionEventsErrors, SessionEventsResponses, SessionReplyData, SessionReplyErrors, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; export type Options = Options2 & { /** @@ -212,8 +212,6 @@ export const getProviderModelInfo = (optio export const getProviderModels = (options: Options) => (options.client ?? client).get({ url: '/config/providers/{name}/models', ...options }); -export const configureProviderOauth = (options: Options) => (options.client ?? client).post({ url: '/config/providers/{name}/oauth', ...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 369e59c60..6575fc7b5 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -2719,32 +2719,6 @@ export type GetProviderModelsResponses = { export type GetProviderModelsResponse = GetProviderModelsResponses[keyof GetProviderModelsResponses]; -export type ConfigureProviderOauthData = { - body?: never; - path: { - /** - * Provider name - */ - name: string; - }; - query?: never; - url: '/config/providers/{name}/oauth'; -}; - -export type ConfigureProviderOauthErrors = { - /** - * OAuth configuration failed - */ - 400: unknown; -}; - -export type ConfigureProviderOauthResponses = { - /** - * OAuth configuration completed - */ - 200: unknown; -}; - export type ReadConfigData = { body: ConfigKeyQuery; path?: never; diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index 464ecfd27..25dec3d58 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -14,8 +14,8 @@ import ModelsBottomBar from './settings/models/bottom_bar/ModelsBottomBar'; import { BottomMenuExtensionSelection } from './bottom_menu/BottomMenuExtensionSelection'; import { cn } from '../utils'; import { AlertType, useAlerts } from './alerts'; -import { useConfig } from './ConfigContext'; import { useModelAndProvider } from './ModelAndProviderContext'; +import { acpListProviderDetails } from '../acp/providers'; import { USE_ACP_CHAT } from '../acpChatFeatureFlag'; import { useAudioRecorder } from '../hooks/useAudioRecorder'; import { toastError } from '../toasts'; @@ -286,7 +286,6 @@ export default function ChatInput({ null ) as React.RefObject; const intl = useIntl(); - const { getProviders } = useConfig(); const { getCurrentModelAndProvider, currentModel: configModel, @@ -609,14 +608,14 @@ export default function ChatInput({ // Priority 2: Check canonical model info (source of truth) const canonicalInfo = await fetchCanonicalModelInfo(provider, model); - if (canonicalInfo?.context_limit) { - setTokenLimit(canonicalInfo.context_limit); + if (canonicalInfo?.contextLimit) { + setTokenLimit(canonicalInfo.contextLimit); setIsTokenLimitLoaded(true); return; } // Priority 3: Fall back to provider metadata known_models (may be outdated) - const providers = await getProviders(true); + const providers = await acpListProviderDetails(); const currentProvider = providers.find((p) => p.name === provider); if (currentProvider?.metadata?.known_models) { const modelConfig = currentProvider.metadata.known_models.find((m) => m.name === model); diff --git a/ui/desktop/src/components/ConfigContext.tsx b/ui/desktop/src/components/ConfigContext.tsx index 834c3d6c0..84704b22f 100644 --- a/ui/desktop/src/components/ConfigContext.tsx +++ b/ui/desktop/src/components/ConfigContext.tsx @@ -1,5 +1,6 @@ import React, { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react'; -import { readAllConfig, readConfig, removeConfig, upsertConfig, providers } from '../api'; +import { readAllConfig, readConfig, removeConfig, upsertConfig } from '../api'; +import { acpListProviderDetails } from '../acp/providers'; import { getConfiguredExtensions, addConfigExtension, @@ -150,8 +151,7 @@ export const ConfigProvider: React.FC = ({ children }) => { const getProviders = useCallback(async (forceRefresh = false): Promise => { if (forceRefresh || providersListRef.current.length === 0) { try { - const response = await providers(); - const providersData = response.data || []; + const providersData = await acpListProviderDetails(); providersListRef.current = providersData; setProvidersList(providersData); return providersData; @@ -172,8 +172,7 @@ export const ConfigProvider: React.FC = ({ children }) => { // Load providers try { - const providersResponse = await providers(); - const providersData = providersResponse.data || []; + const providersData = await acpListProviderDetails(); providersListRef.current = providersData; setProvidersList(providersData); } catch (error) { diff --git a/ui/desktop/src/components/ModelAndProviderContext.tsx b/ui/desktop/src/components/ModelAndProviderContext.tsx index 8b5f19d62..077c3b349 100644 --- a/ui/desktop/src/components/ModelAndProviderContext.tsx +++ b/ui/desktop/src/components/ModelAndProviderContext.tsx @@ -1,8 +1,8 @@ import React, { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react'; import { toastError, toastSuccess } from '../toasts'; import Model, { getProviderMetadata } from './settings/models/modelInterface'; -import { ProviderMetadata, setConfigProvider, updateAgentProvider } from '../api'; -import { useConfig } from './ConfigContext'; +import { ProviderMetadata } from '../api'; +import { acpReadDefaults, acpSaveDefaults, acpSetSessionProviderModel } from '../acp/providers'; import { errorMessage } from '../utils/conversionUtils'; import { getModelDisplayName, @@ -60,7 +60,6 @@ export { i18n as modelAndProviderMessages }; export const ModelAndProviderProvider: React.FC = ({ children }) => { const [currentModel, setCurrentModel] = useState(null); const [currentProvider, setCurrentProvider] = useState(null); - const { read, getProviders } = useConfig(); const intl = useIntl(); const changeModel = useCallback( @@ -71,31 +70,14 @@ export const ModelAndProviderProvider: React.FC = try { if (sessionId) { - const response = await updateAgentProvider({ - body: { - session_id: sessionId, - provider: providerName, - model: modelName, - context_limit: model.context_limit, - request_params: model.request_params, - }, - }); - if (response.error) { - throw new Error(`Failed to update agent provider: ${response.error}`); - } + await acpSetSessionProviderModel(sessionId, providerName, modelName); } // Only update the global config default when there's no session // (i.e. changing from settings, not from within an existing chat) if (!sessionId) { phase = 'config'; - await setConfigProvider({ - body: { - provider: providerName, - model: modelName, - }, - throwOnError: true, - }); + await acpSaveDefaults(providerName, modelName); } if (!sessionId) { @@ -132,13 +114,7 @@ export const ModelAndProviderProvider: React.FC = const model = window.appConfig.get('GOOSE_DEFAULT_MODEL') as string; if (provider && model) { try { - await setConfigProvider({ - body: { - provider: provider, - model: model, - }, - throwOnError: true, - }); + await acpSaveDefaults(provider, model); } catch (error) { console.error('[getFallbackModelAndProvider] Failed to write to config', error); } @@ -147,22 +123,22 @@ export const ModelAndProviderProvider: React.FC = }, []); const getCurrentModelAndProvider = useCallback(async () => { - let model: string; - let provider: string; + let model: string | null; + let provider: string | null; - // read from config try { - model = (await read('GOOSE_MODEL', false)) as string; - provider = (await read('GOOSE_PROVIDER', false)) as string; + const defaults = await acpReadDefaults(); + model = defaults.modelId; + provider = defaults.providerId; } catch { - console.error(`Failed to read GOOSE_MODEL or GOOSE_PROVIDER from config`); - throw new Error('Failed to read GOOSE_MODEL or GOOSE_PROVIDER from config'); + console.error(`Failed to read default model or provider`); + throw new Error('Failed to read default model or provider'); } if (!model || !provider) { return getFallbackModelAndProvider(); } return { model: model, provider: provider }; - }, [read, getFallbackModelAndProvider]); + }, [getFallbackModelAndProvider]); const getCurrentModelAndProviderForDisplay = useCallback(async () => { const modelProvider = await getCurrentModelAndProvider(); @@ -173,28 +149,28 @@ export const ModelAndProviderProvider: React.FC = let metadata: ProviderMetadata; try { - metadata = await getProviderMetadata(String(gooseProvider), getProviders); + metadata = await getProviderMetadata(String(gooseProvider)); } catch { return { model: gooseModel, provider: gooseProvider }; } const providerDisplayName = metadata.display_name; return { model: gooseModel, provider: providerDisplayName }; - }, [getCurrentModelAndProvider, getProviders]); + }, [getCurrentModelAndProvider]); const getCurrentModelDisplayName = useCallback(async () => { try { - const currentModelName = (await read('GOOSE_MODEL', false)) as string; - return getModelDisplayName(currentModelName); + const { modelId } = await acpReadDefaults(); + return getModelDisplayName(modelId ?? ''); } catch { return intl.formatMessage(i18n.selectModel); } - }, [read, intl]); + }, [intl]); const getCurrentProviderDisplayName = useCallback(async () => { try { - const currentModelName = (await read('GOOSE_MODEL', false)) as string; - const providerDisplayName = getProviderDisplayName(currentModelName); + const { modelId } = await acpReadDefaults(); + const providerDisplayName = getProviderDisplayName(modelId ?? ''); if (providerDisplayName) { return providerDisplayName; } @@ -204,7 +180,7 @@ export const ModelAndProviderProvider: React.FC = } catch { return ''; } - }, [read, getCurrentModelAndProviderForDisplay]); + }, [getCurrentModelAndProviderForDisplay]); const refreshCurrentModelAndProvider = useCallback(async () => { try { diff --git a/ui/desktop/src/components/bottom_menu/CostTracker.tsx b/ui/desktop/src/components/bottom_menu/CostTracker.tsx index 323beb3ee..bbfd9ff23 100644 --- a/ui/desktop/src/components/bottom_menu/CostTracker.tsx +++ b/ui/desktop/src/components/bottom_menu/CostTracker.tsx @@ -1,8 +1,7 @@ import { useState, useEffect } from 'react'; import { CoinIcon } from '../icons'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/Tooltip'; -import { fetchCanonicalModelInfo } from '../../utils/canonical'; -import type { ModelInfoData } from '../../api'; +import { fetchCanonicalModelInfo, type CanonicalModelInfo } from '../../utils/canonical'; import { defineMessages, useIntl } from '../../i18n'; const i18n = defineMessages({ @@ -40,7 +39,7 @@ export function CostTracker({ provider: currentProvider, }: CostTrackerProps) { const intl = useIntl(); - const [costInfo, setCostInfo] = useState(null); + const [costInfo, setCostInfo] = useState(null); const [isLoading, setIsLoading] = useState(true); const [showPricing, setShowPricing] = useState(true); const [pricingFailed, setPricingFailed] = useState(false); @@ -118,7 +117,7 @@ export function CostTracker({ if ( accumulatedCost == null && (!costInfo || - (costInfo.input_token_cost === undefined && costInfo.output_token_cost === undefined)) + (costInfo.inputTokenCost === undefined && costInfo.outputTokenCost === undefined)) ) { const freeProviders = ['ollama', 'local', 'localhost']; if (freeProviders.includes(currentProvider.toLowerCase())) { @@ -170,14 +169,14 @@ export function CostTracker({ return intl.formatMessage(i18n.totalSessionCost, { cost: `${currency}${totalCost.toFixed(4)}` }) + `\n` + intl.formatMessage(i18n.inputOutputTooltip, { inputTokens: inputTokens.toLocaleString(), - inputCost: `${currency}${((inputTokens * (costInfo?.input_token_cost || 0)) / 1_000_000).toFixed(6)}`, + inputCost: `${currency}${((inputTokens * (costInfo?.inputTokenCost || 0)) / 1_000_000).toFixed(6)}`, outputTokens: outputTokens.toLocaleString(), - outputCost: `${currency}${((outputTokens * (costInfo?.output_token_cost || 0)) / 1_000_000).toFixed(6)}`, + outputCost: `${currency}${((outputTokens * (costInfo?.outputTokenCost || 0)) / 1_000_000).toFixed(6)}`, }); } - const inputCostStr = `${currency}${((inputTokens * (costInfo?.input_token_cost || 0)) / 1_000_000).toFixed(6)}`; - const outputCostStr = `${currency}${((outputTokens * (costInfo?.output_token_cost || 0)) / 1_000_000).toFixed(6)}`; + const inputCostStr = `${currency}${((inputTokens * (costInfo?.inputTokenCost || 0)) / 1_000_000).toFixed(6)}`; + const outputCostStr = `${currency}${((outputTokens * (costInfo?.outputTokenCost || 0)) / 1_000_000).toFixed(6)}`; return intl.formatMessage(i18n.inputOutputTooltip, { inputTokens: inputTokens.toLocaleString(), inputCost: inputCostStr, diff --git a/ui/desktop/src/components/onboarding/OnboardingGuard.tsx b/ui/desktop/src/components/onboarding/OnboardingGuard.tsx index 59e543679..97325ad6d 100644 --- a/ui/desktop/src/components/onboarding/OnboardingGuard.tsx +++ b/ui/desktop/src/components/onboarding/OnboardingGuard.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useConfig } from '../ConfigContext'; import { useModelAndProvider } from '../ModelAndProviderContext'; +import { acpListProviderDetails, acpReadDefaults, acpSaveDefaults } from '../../acp/providers'; import { Goose } from '../icons'; import { Button } from '../ui/button'; import ProviderSelector from './ProviderSelector'; @@ -47,7 +48,7 @@ interface OnboardingGuardProps { export default function OnboardingGuard({ children }: OnboardingGuardProps) { const intl = useIntl(); const navigate = useNavigate(); - const { read, upsert, getProviders } = useConfig(); + const { upsert } = useConfig(); const { getFallbackModelAndProvider, refreshCurrentModelAndProvider } = useModelAndProvider(); const [isCheckingProvider, setIsCheckingProvider] = useState(true); @@ -66,7 +67,7 @@ export default function OnboardingGuard({ children }: OnboardingGuardProps) { setCheckProviderError(false); for (let attempt = 0; attempt <= retries; attempt++) { try { - const provider = (await read('GOOSE_PROVIDER', false, { throwOnError: true })) as string | null; + const { providerId: provider } = await acpReadDefaults(); if (provider?.trim()) { setHasProvider(true); setIsCheckingProvider(false); @@ -75,8 +76,8 @@ export default function OnboardingGuard({ children }: OnboardingGuardProps) { const fallback = await getFallbackModelAndProvider(); if (fallback.provider?.trim() && fallback.model?.trim()) { - const configuredProvider = (await read('GOOSE_PROVIDER', false)) as string | null; - const configuredModel = (await read('GOOSE_MODEL', false)) as string | null; + const { providerId: configuredProvider, modelId: configuredModel } = + await acpReadDefaults(); if (configuredProvider?.trim() && configuredModel?.trim()) { await refreshCurrentModelAndProvider(); setHasProvider(true); @@ -113,16 +114,11 @@ export default function OnboardingGuard({ children }: OnboardingGuardProps) { const handleConfigured = async (providerName: string, modelId?: string) => { trackOnboardingProviderSelected({ provider: providerName }); - await upsert('GOOSE_PROVIDER', providerName, false); - const providers = await getProviders(true); + const providers = await acpListProviderDetails(); const matchedProvider = providers.find((p) => p.name === providerName); - if (modelId) { - await upsert('GOOSE_MODEL', modelId, false); - setConfiguredModel(modelId); - } else if (matchedProvider) { - await upsert('GOOSE_MODEL', matchedProvider.metadata.default_model, false); - setConfiguredModel(matchedProvider.metadata.default_model); - } + const resolvedModel = modelId ?? matchedProvider?.metadata.default_model ?? null; + await acpSaveDefaults(providerName, resolvedModel); + setConfiguredModel(resolvedModel); await refreshCurrentModelAndProvider(); setConfiguredProvider(providerName); setConfiguredProviderDisplayName(matchedProvider?.metadata.display_name || providerName); diff --git a/ui/desktop/src/components/onboarding/ProviderConfigForm.tsx b/ui/desktop/src/components/onboarding/ProviderConfigForm.tsx index ebf1acd6b..dde92a8a5 100644 --- a/ui/desktop/src/components/onboarding/ProviderConfigForm.tsx +++ b/ui/desktop/src/components/onboarding/ProviderConfigForm.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { configureProviderOauth, ProviderDetails } from '../../api'; -import { useConfig } from '../ConfigContext'; +import { ProviderDetails } from '../../api'; +import { acpAuthenticateProvider } from '../../acp/providers'; import DefaultProviderSetupForm, { ConfigInput, } from '../settings/providers/modal/subcomponents/forms/DefaultProviderSetupForm'; @@ -78,10 +78,7 @@ function OAuthForm({ const handleLogin = async () => { setIsLoading(true); try { - await configureProviderOauth({ - path: { name: provider.name }, - throwOnError: true, - }); + await acpAuthenticateProvider(provider.name); onConfigured(provider.name); } catch (err) { onError(`Sign-in failed: ${err instanceof Error ? err.message : String(err)}`); @@ -101,7 +98,9 @@ function OAuthForm({ size="lg" > - {isLoading ? intl.formatMessage(i18n.signingIn) : intl.formatMessage(i18n.signInWith, { providerName: provider.metadata.display_name })} + {isLoading + ? intl.formatMessage(i18n.signingIn) + : intl.formatMessage(i18n.signInWith, { providerName: provider.metadata.display_name })}

{isDeviceCodeFlow @@ -122,7 +121,6 @@ function ApiKeyForm({ onError: (msg: string) => void; }) { const intl = useIntl(); - const { upsert } = useConfig(); const [configValues, setConfigValues] = useState>({}); const [validationErrors, setValidationErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); @@ -158,7 +156,7 @@ function ApiKeyForm({ setIsSubmitting(true); try { - await providerConfigSubmitHandler(upsert, provider, toSubmit); + await providerConfigSubmitHandler(provider, toSubmit); onConfigured(provider.name); } catch (err) { const msg = diff --git a/ui/desktop/src/components/onboarding/ProviderSelector.tsx b/ui/desktop/src/components/onboarding/ProviderSelector.tsx index ceb58ffc9..9cb222d57 100644 --- a/ui/desktop/src/components/onboarding/ProviderSelector.tsx +++ b/ui/desktop/src/components/onboarding/ProviderSelector.tsx @@ -1,10 +1,9 @@ import { useState, useEffect, useMemo } from 'react'; +import { ProviderDetails, UpdateCustomProviderRequest } from '../../api'; import { - providers as fetchProviders, - createCustomProvider, - ProviderDetails, - UpdateCustomProviderRequest, -} from '../../api'; + acpCreateCustomProviderFromRequest, + acpListProviderDetails, +} from '../../acp/providers'; import { Select } from '../ui/Select'; import ProviderConfigForm from './ProviderConfigForm'; import FreeOptionCards from './FreeOptionCards'; @@ -73,13 +72,8 @@ export default function ProviderSelector({ useEffect(() => { const load = async () => { try { - const response = await fetchProviders({ throwOnError: true }); - if (response.data) { - const list = Array.isArray(response.data) - ? response.data - : (response.data as { providers: ProviderDetails[] }).providers || []; - setProviderList(list); - } + const list = await acpListProviderDetails(); + setProviderList(list); } catch (err) { console.error('Failed to fetch providers:', err); } @@ -127,10 +121,10 @@ export default function ProviderSelector({ }; const handleCreateCustomProvider = async (data: UpdateCustomProviderRequest) => { - const result = await createCustomProvider({ body: data, throwOnError: true }); + const result = await acpCreateCustomProviderFromRequest(data); setShowCustomModal(false); - if (result.data?.provider_name) { - onConfigured(result.data.provider_name); + if (result.provider_name) { + onConfigured(result.provider_name); } }; diff --git a/ui/desktop/src/components/recipes/shared/RecipeModelSelector.tsx b/ui/desktop/src/components/recipes/shared/RecipeModelSelector.tsx index ad616bfdb..9a9c3589b 100644 --- a/ui/desktop/src/components/recipes/shared/RecipeModelSelector.tsx +++ b/ui/desktop/src/components/recipes/shared/RecipeModelSelector.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useCallback } from 'react'; import { Select } from '../../ui/Select'; import { Input } from '../../ui/input'; -import { useConfig } from '../../ConfigContext'; +import { acpListProviderDetails } from '../../../acp/providers'; import { fetchModelsForProviders } from '../../settings/models/modelInterface'; import { defineMessages, useIntl } from '../../../i18n'; @@ -70,7 +70,6 @@ export const RecipeModelSelector = ({ onModelChange, }: RecipeModelSelectorProps) => { const intl = useIntl(); - const { getProviders } = useConfig(); const [providerOptions, setProviderOptions] = useState<{ value: string; label: string }[]>([]); const [modelOptions, setModelOptions] = useState< { options: { value: string; label: string; provider: string }[] }[] @@ -83,7 +82,7 @@ export const RecipeModelSelector = ({ (async () => { try { setFetchError(null); - const providersResponse = await getProviders(false); + const providersResponse = await acpListProviderDetails(); const activeProviders = providersResponse.filter((provider) => provider.is_configured); setProviderOptions([ @@ -132,7 +131,7 @@ export const RecipeModelSelector = ({ setLoadingModels(false); } })(); - }, [getProviders, intl]); + }, [intl]); useEffect(() => { if (!loadingModels && selectedModel && selectedProvider) { diff --git a/ui/desktop/src/components/settings/auth/AuthSettingsSection.test.tsx b/ui/desktop/src/components/settings/auth/AuthSettingsSection.test.tsx index 5a531f37f..505d4c5f7 100644 --- a/ui/desktop/src/components/settings/auth/AuthSettingsSection.test.tsx +++ b/ui/desktop/src/components/settings/auth/AuthSettingsSection.test.tsx @@ -3,23 +3,19 @@ import { render, screen, waitFor, type RenderOptions } from '@testing-library/re import userEvent from '@testing-library/user-event'; import AuthSettingsSection from './AuthSettingsSection'; import { - configureProviderOauth, - deleteProviderSecret, - listProviderSecrets, - ProviderSecret, -} from '../../../api'; + acpAuthenticateProvider, + acpDeleteProviderSecret, + acpListProviderSecrets, + type ProviderSecretDto, +} from '../../../acp/providers'; import { IntlTestWrapper } from '../../../i18n/test-utils'; import { toast } from 'react-toastify'; -vi.mock('../../../api', async () => { - const actual = await vi.importActual('../../../api'); - return { - ...actual, - configureProviderOauth: vi.fn(), - listProviderSecrets: vi.fn(), - deleteProviderSecret: vi.fn(), - }; -}); +vi.mock('../../../acp/providers', () => ({ + acpAuthenticateProvider: vi.fn(), + acpListProviderSecrets: vi.fn(), + acpDeleteProviderSecret: vi.fn(), +})); vi.mock('../../ModelAndProviderContext', () => ({ useModelAndProvider: () => ({ @@ -34,41 +30,35 @@ vi.mock('react-toastify', () => ({ }, })); -const mockedListProviderSecrets = vi.mocked(listProviderSecrets); -const mockedDeleteProviderSecret = vi.mocked(deleteProviderSecret); -const mockedConfigureProviderOauth = vi.mocked(configureProviderOauth); +const mockedListProviderSecrets = vi.mocked(acpListProviderSecrets); +const mockedDeleteProviderSecret = vi.mocked(acpDeleteProviderSecret); +const mockedAcpAuthenticateProvider = vi.mocked(acpAuthenticateProvider); const mockedToast = vi.mocked(toast); const renderWithIntl = (ui: React.ReactElement, options?: RenderOptions) => render(ui, { wrapper: IntlTestWrapper, ...options }); -const providerSecret: ProviderSecret = { +const providerSecret: ProviderSecretDto = { id: 'secret_store:openai:OPENAI_API_KEY', provider: 'openai', - provider_display_name: 'OpenAI', + providerDisplayName: 'OpenAI', name: 'OPENAI_API_KEY', storage: 'secret_store', - expires_at: null, + expiresAt: null, status: 'unknown', configured: true, - has_secret: true, - can_delete: true, - can_configure: false, - configure_provider: null, + hasSecret: true, + canDelete: true, + canConfigure: false, + configureProvider: null, }; -const apiResult = (data: T) => ({ - data, - request: {} as never, - response: {} as never, -}); - describe('AuthSettingsSection', () => { beforeEach(() => { vi.clearAllMocks(); - mockedListProviderSecrets.mockResolvedValue(apiResult({ secrets: [] })); - mockedDeleteProviderSecret.mockResolvedValue(apiResult('ok')); - mockedConfigureProviderOauth.mockResolvedValue(apiResult('ok')); + mockedListProviderSecrets.mockResolvedValue([]); + mockedDeleteProviderSecret.mockResolvedValue(undefined); + mockedAcpAuthenticateProvider.mockResolvedValue(undefined); }); it('renders an empty state when no credentials are stored', async () => { @@ -79,17 +69,13 @@ describe('AuthSettingsSection', () => { }); it('renders provider credentials with storage and expiry status', async () => { - mockedListProviderSecrets.mockResolvedValue( - apiResult({ - secrets: [ - { - ...providerSecret, - expires_at: '2027-01-01T12:00:00Z', - status: 'valid', - }, - ], - }) - ); + mockedListProviderSecrets.mockResolvedValue([ + { + ...providerSecret, + expiresAt: '2027-01-01T12:00:00Z', + status: 'valid', + }, + ]); renderWithIntl(); @@ -100,7 +86,7 @@ describe('AuthSettingsSection', () => { }); it('does not render an expiry badge when expiry is unknown', async () => { - mockedListProviderSecrets.mockResolvedValue(apiResult({ secrets: [providerSecret] })); + mockedListProviderSecrets.mockResolvedValue([providerSecret]); renderWithIntl(); @@ -113,8 +99,8 @@ describe('AuthSettingsSection', () => { it('deletes a credential after confirmation and refreshes the list', async () => { const user = userEvent.setup(); mockedListProviderSecrets - .mockResolvedValueOnce(apiResult({ secrets: [providerSecret] })) - .mockResolvedValueOnce(apiResult({ secrets: [] })); + .mockResolvedValueOnce([providerSecret]) + .mockResolvedValueOnce([]); renderWithIntl(); @@ -132,10 +118,7 @@ describe('AuthSettingsSection', () => { await user.click(screen.getByRole('button', { name: 'Delete' })); await waitFor(() => { - expect(mockedDeleteProviderSecret).toHaveBeenCalledWith({ - path: { id: 'secret_store:openai:OPENAI_API_KEY' }, - throwOnError: true, - }); + expect(mockedDeleteProviderSecret).toHaveBeenCalledWith('secret_store:openai:OPENAI_API_KEY'); }); await waitFor(() => { expect(mockedToast.success).toHaveBeenCalledWith('Credential deleted'); @@ -145,35 +128,31 @@ describe('AuthSettingsSection', () => { it('configures the permanent Hugging Face credential row', async () => { const user = userEvent.setup(); - const huggingFaceSecret: ProviderSecret = { + const huggingFaceSecret: ProviderSecretDto = { id: 'provider_cache:huggingface', provider: 'huggingface', - provider_display_name: 'Hugging Face', + providerDisplayName: 'Hugging Face', name: 'OAuth token', storage: 'provider_cache', - expires_at: null, + expiresAt: null, status: 'unknown', configured: false, - has_secret: false, - can_delete: false, - can_configure: true, - configure_provider: 'huggingface', + hasSecret: false, + canDelete: false, + canConfigure: true, + configureProvider: 'huggingface', }; mockedListProviderSecrets - .mockResolvedValueOnce(apiResult({ secrets: [huggingFaceSecret] })) - .mockResolvedValueOnce( - apiResult({ - secrets: [ - { - ...huggingFaceSecret, - configured: true, - has_secret: true, - can_delete: true, - }, - ], - }) - ); + .mockResolvedValueOnce([huggingFaceSecret]) + .mockResolvedValueOnce([ + { + ...huggingFaceSecret, + configured: true, + hasSecret: true, + canDelete: true, + }, + ]); renderWithIntl(); @@ -182,10 +161,7 @@ describe('AuthSettingsSection', () => { await user.click(screen.getByRole('button', { name: 'Sign in' })); await waitFor(() => { - expect(mockedConfigureProviderOauth).toHaveBeenCalledWith({ - path: { name: 'huggingface' }, - throwOnError: true, - }); + expect(mockedAcpAuthenticateProvider).toHaveBeenCalledWith('huggingface'); }); await waitFor(() => { expect(mockedToast.success).toHaveBeenCalledWith('Credential configured'); diff --git a/ui/desktop/src/components/settings/auth/AuthSettingsSection.tsx b/ui/desktop/src/components/settings/auth/AuthSettingsSection.tsx index 35b321fd2..c3d4c6c8b 100644 --- a/ui/desktop/src/components/settings/auth/AuthSettingsSection.tsx +++ b/ui/desktop/src/components/settings/auth/AuthSettingsSection.tsx @@ -2,11 +2,11 @@ import { useCallback, useEffect, useState } from 'react'; import { KeyRound, Loader2, LogIn, RefreshCw, Trash2 } from 'lucide-react'; import { toast } from 'react-toastify'; import { - configureProviderOauth, - deleteProviderSecret, - listProviderSecrets, - ProviderSecret, -} from '../../../api'; + acpAuthenticateProvider, + acpDeleteProviderSecret, + acpListProviderSecrets, + type ProviderSecretDto, +} from '../../../acp/providers'; import { errorMessage } from '../../../utils/conversionUtils'; import { useModelAndProvider } from '../../ModelAndProviderContext'; import { Button } from '../../ui/button'; @@ -97,26 +97,26 @@ const i18n = defineMessages({ }, }); -function storageLabel(secret: ProviderSecret, intl: ReturnType) { +function storageLabel(secret: ProviderSecretDto, intl: ReturnType) { if (secret.storage === 'provider_cache') { return intl.formatMessage(i18n.storageProviderCache); } return intl.formatMessage(i18n.storageSecretStore); } -function expiryLabel(secret: ProviderSecret, intl: ReturnType) { - if (!secret.expires_at) { +function expiryLabel(secret: ProviderSecretDto, intl: ReturnType) { + if (!secret.expiresAt) { return null; } return intl.formatMessage(i18n.expiresAt, { - date: intl.formatDate(new Date(secret.expires_at), { + date: intl.formatDate(new Date(secret.expiresAt), { dateStyle: 'medium', timeStyle: 'short', }), }); } -function expiryClass(secret: ProviderSecret) { +function expiryClass(secret: ProviderSecretDto) { if (secret.status === 'expired') { return 'border-red-500/30 bg-red-500/10 text-red-700 dark:text-red-300'; } @@ -126,17 +126,17 @@ function expiryClass(secret: ProviderSecret) { export default function AuthSettingsSection() { const intl = useIntl(); const { currentProvider } = useModelAndProvider(); - const [secrets, setSecrets] = useState([]); + const [secrets, setSecrets] = useState([]); const [loading, setLoading] = useState(true); const [deletingId, setDeletingId] = useState(null); const [configuringId, setConfiguringId] = useState(null); - const [secretToDelete, setSecretToDelete] = useState(null); + const [secretToDelete, setSecretToDelete] = useState(null); const loadSecrets = useCallback(async () => { setLoading(true); try { - const response = await listProviderSecrets({ throwOnError: true }); - setSecrets(response.data?.secrets ?? []); + const secrets = await acpListProviderSecrets(); + setSecrets(secrets); } catch { toast.error(intl.formatMessage(i18n.failedToLoad)); setSecrets([]); @@ -156,10 +156,7 @@ export default function AuthSettingsSection() { setDeletingId(secretToDelete.id); try { - await deleteProviderSecret({ - path: { id: secretToDelete.id }, - throwOnError: true, - }); + await acpDeleteProviderSecret(secretToDelete.id); toast.success(intl.formatMessage(i18n.deleted)); setSecretToDelete(null); await loadSecrets(); @@ -174,17 +171,14 @@ export default function AuthSettingsSection() { } }; - const configureSecret = async (secret: ProviderSecret) => { - if (!secret.configure_provider) { + const configureSecret = async (secret: ProviderSecretDto) => { + if (!secret.configureProvider) { return; } setConfiguringId(secret.id); try { - await configureProviderOauth({ - path: { name: secret.configure_provider }, - throwOnError: true, - }); + await acpAuthenticateProvider(secret.configureProvider); toast.success(intl.formatMessage(i18n.signedIn)); await loadSecrets(); } catch (error) { @@ -229,7 +223,7 @@ export default function AuthSettingsSection() {

- {secret.provider_display_name} + {secret.providerDisplayName}

{storageLabel(secret, intl)} @@ -247,7 +241,7 @@ export default function AuthSettingsSection() {

- {secret.can_configure && secret.configure_provider && ( + {secret.canConfigure && secret.configureProvider && ( )} - {secret.can_delete && ( + {secret.canDelete && (
-
{intl.formatMessage(i18n.currentModel)}
+
+ {intl.formatMessage(i18n.currentModel)} +

{menuModelLabel} {!isModelLoading && displayProvider && ` — ${displayProvider}`}

{shouldShowResolvedModel && resolvedDisplayModelName && (
-
{intl.formatMessage(i18n.resolvedModel)}
+
+ {intl.formatMessage(i18n.resolvedModel)} +

{resolvedDisplayModelName}

@@ -204,7 +208,9 @@ export default function ModelsBottomBar({

- {intl.formatMessage(i18n.localModelSettingsTitle, { modelName: getModelDisplayName(currentModel) })} + {intl.formatMessage(i18n.localModelSettingsTitle, { + modelName: getModelDisplayName(currentModel), + })}

- +
)} diff --git a/ui/desktop/src/components/settings/providers/modal/subcomponents/forms/DefaultProviderSetupForm.tsx b/ui/desktop/src/components/settings/providers/modal/subcomponents/forms/DefaultProviderSetupForm.tsx index 9d8a4ab82..9ebb333f3 100644 --- a/ui/desktop/src/components/settings/providers/modal/subcomponents/forms/DefaultProviderSetupForm.tsx +++ b/ui/desktop/src/components/settings/providers/modal/subcomponents/forms/DefaultProviderSetupForm.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState, useCallback } from 'react'; import { Input } from '../../../../../ui/input'; -import { useConfig } from '../../../../../ConfigContext'; +import { acpReadProviderConfig } from '../../../../../../acp/providers'; import { ProviderDetails, ConfigKey } from '../../../../../../api'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../../../../../ui/collapsible'; import { configLabels, configPlaceholders } from '../../../../../../utils/configUtils'; @@ -95,19 +95,30 @@ export default function DefaultProviderSetupForm({ const intl = useIntl(); const [isLoading, setIsLoading] = useState(true); const [optionalExpanded, setOptionalExpanded] = useState(false); - const { read } = useConfig(); const loadConfigValues = useCallback(async () => { setIsLoading(true); try { const values: { [k: string]: ConfigInput } = {}; - for (const parameter of parameters) { - const configKey = `${parameter.name}`; - const configValue = (await read(configKey, parameter.secret || false)) as ConfigValue; + let fields: Awaited> = []; + try { + fields = await acpReadProviderConfig(provider.name); + } catch { + // Provider may not be in the registry yet; fall back to defaults below. + } + const fieldByKey = new Map(fields.map((field) => [field.key, field])); - if (configValue !== undefined && configValue !== null) { - values[parameter.name] = { serverValue: configValue }; + for (const parameter of parameters) { + const field = fieldByKey.get(parameter.name); + + if (field?.isSet && field.value != null) { + // Secrets come back masked from the server; preserve the masked shape + // so the form renders a placeholder rather than the raw value. + const serverValue: ConfigValue = parameter.secret + ? { maskedValue: field.value } + : field.value; + values[parameter.name] = { serverValue }; } else if (parameter.default !== undefined && parameter.default !== null) { values[parameter.name] = { value: parameter.default }; } @@ -120,7 +131,7 @@ export default function DefaultProviderSetupForm({ } finally { setIsLoading(false); } - }, [parameters, read, setConfigValues]); + }, [parameters, provider.name, setConfigValues]); useEffect(() => { loadConfigValues(); diff --git a/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx b/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx index 26a853f60..6cb5ec273 100644 --- a/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx +++ b/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx @@ -1,112 +1,29 @@ -import { getProviderModels, readConfig } from '../../../../../../api'; +import { acpSaveProviderConfig } from '../../../../../../acp/providers'; /** - * Standalone function to submit provider configuration - * Useful for components that don't want to use the hook + * Submit provider configuration through ACP. + * + * The ACP server validates the supplied fields, persists config/secret values, + * and triggers an inventory refresh in a single call, so no client-side + * rollback is required. */ export const providerConfigSubmitHandler = async ( - upsertFn: (key: string, value: unknown, isSecret: boolean) => Promise, provider: { name: string; metadata: { - config_keys?: Array<{ - name: string; - required?: boolean; - default?: unknown; - secret?: boolean; - }>; + config_keys?: Array<{ name: string; default?: unknown }>; }; }, configValues: Record ) => { - const parameters = provider.metadata.config_keys || []; - - // Save current NON-SECRET config values for rollback on failure - // We skip secrets because readConfig returns masked values for secrets, - // and upserting those masked values would corrupt the actual secret - const previousConfigValues: Record = {}; - const nonSecretParams = parameters.filter((param) => !param.secret); - - await Promise.all( - nonSecretParams.map(async (param) => { - try { - const currentValue = await readConfig({ - body: { key: param.name, is_secret: false }, - }); - if (currentValue.data) { - previousConfigValues[param.name] = { - value: currentValue.data, - isSecret: false, - }; - } - } catch { - // No previous value exists, that's fine - } - }) - ); - - const requiredParams = parameters.filter((param) => param.required); - if (requiredParams.length === 0 && parameters.length > 0) { - const allOptionalWithDefaults = parameters.every( - (param) => !param.required && param.default !== undefined - ); - if (allOptionalWithDefaults) { - const promises: Promise[] = []; - - for (const param of parameters) { - if (param.default !== undefined) { - const value = - configValues[param.name] !== undefined ? configValues[param.name] : param.default; - promises.push(upsertFn(param.name, value, param.secret === true)); - } - } - - await Promise.all(promises); - return; + const fields: { key: string; value: string }[] = []; + for (const { name, default: defaultValue } of provider.metadata.config_keys ?? []) { + const value = configValues[name] ?? defaultValue; + if (value === undefined || value === null || value === '') { + continue; } + fields.push({ key: name, value: String(value) }); } - const upsertPromises = parameters.map( - async (parameter: { - name: string; - required?: boolean; - default?: unknown; - secret?: boolean; - }) => { - if (!configValues[parameter.name] && !parameter.required) { - return; - } - - const value = - configValues[parameter.name] !== undefined - ? configValues[parameter.name] - : parameter.default; - - if (value === undefined || value === null) { - return; - } - - const configKey = `${parameter.name}`; - const isSecret = parameter.secret === true; - - await upsertFn(configKey, value, isSecret); - } - ); - - await Promise.all(upsertPromises); - - try { - await getProviderModels({ - path: { name: provider.name }, - throwOnError: true, - }); - } catch (error) { - const rollbackPromises: Promise[] = []; - for (const [key, { value, isSecret }] of Object.entries(previousConfigValues)) { - rollbackPromises.push(upsertFn(key, value, isSecret)); - } - await Promise.all(rollbackPromises); - - throw error; - } + await acpSaveProviderConfig(provider.name, fields); }; diff --git a/ui/desktop/src/components/settings/reset_provider/ResetProviderSection.tsx b/ui/desktop/src/components/settings/reset_provider/ResetProviderSection.tsx index 3a457e1f5..18c7ca776 100644 --- a/ui/desktop/src/components/settings/reset_provider/ResetProviderSection.tsx +++ b/ui/desktop/src/components/settings/reset_provider/ResetProviderSection.tsx @@ -1,6 +1,6 @@ import { Button } from '../../ui/button'; import { RefreshCw } from 'lucide-react'; -import { useConfig } from '../../ConfigContext'; +import { acpClearDefaults } from '../../../acp/providers'; import { View, ViewOptions } from '../../../utils/navigationUtils'; import { defineMessages, useIntl } from '../../../i18n'; @@ -21,12 +21,10 @@ interface ResetProviderSectionProps { export default function ResetProviderSection(_props: ResetProviderSectionProps) { const intl = useIntl(); - const { remove } = useConfig(); const handleResetProvider = async () => { try { - await remove('GOOSE_PROVIDER', false); - await remove('GOOSE_MODEL', false); + await acpClearDefaults(); window.location.reload(); } catch (error) { diff --git a/ui/desktop/src/utils/canonical.ts b/ui/desktop/src/utils/canonical.ts index 6ee98da60..0b0ac91e9 100644 --- a/ui/desktop/src/utils/canonical.ts +++ b/ui/desktop/src/utils/canonical.ts @@ -2,7 +2,9 @@ * Utilities for fetching canonical model information from the backend */ -import { getCanonicalModelInfo, type ModelInfoData } from '../api'; +import { acpGetCanonicalModelInfo, type CanonicalModelInfoDto } from '../acp/providers'; + +export type CanonicalModelInfo = CanonicalModelInfoDto; /** * Fetch canonical model info (pricing + context limits) for a specific provider/model @@ -10,14 +12,9 @@ import { getCanonicalModelInfo, type ModelInfoData } from '../api'; export async function fetchCanonicalModelInfo( provider: string, model: string -): Promise { +): Promise { try { - const response = await getCanonicalModelInfo({ - body: { provider, model }, - throwOnError: true, - }); - - return response.data.model_info ?? null; + return await acpGetCanonicalModelInfo(provider, model); } catch { return null; } diff --git a/ui/sdk/src/generated/client.gen.ts b/ui/sdk/src/generated/client.gen.ts index aa14543de..4455796c4 100644 --- a/ui/sdk/src/generated/client.gen.ts +++ b/ui/sdk/src/generated/client.gen.ts @@ -18,6 +18,8 @@ import type { AppsListRequest_unstable, AppsListResponse_unstable, ArchiveSessionRequest_unstable, + CanonicalModelInfoRequest_unstable, + CanonicalModelInfoResponse_unstable, CreateScheduleRequest_unstable, CreateScheduleResponse_unstable, CreateSourceRequest_unstable, @@ -32,6 +34,7 @@ import type { CustomProviderUpdateResponse_unstable, DecodeRecipeRequest_unstable, DecodeRecipeResponse_unstable, + DefaultsClearRequest_unstable, DefaultsReadRequest_unstable, DefaultsReadResponse_unstable, DefaultsSaveRequest_unstable, @@ -124,6 +127,9 @@ import type { ProviderConfigSaveRequest_unstable, ProviderConfigStatusRequest_unstable, ProviderConfigStatusResponse_unstable, + ProviderSecretDeleteRequest_unstable, + ProviderSecretsListRequest_unstable, + ProviderSecretsListResponse_unstable, ProviderSetupCatalogListRequest_unstable, ProviderSetupCatalogListResponse_unstable, ProviderSupportedModelsListRequest_unstable, @@ -171,6 +177,7 @@ import { zAppsExportResponse_unstable, zAppsImportResponse_unstable, zAppsListResponse_unstable, + zCanonicalModelInfoResponse_unstable, zCreateScheduleResponse_unstable, zCreateSourceResponse_unstable, zCustomProviderCreateResponse_unstable, @@ -217,6 +224,7 @@ import { zProviderConfigChangeResponse_unstable, zProviderConfigReadResponse_unstable, zProviderConfigStatusResponse_unstable, + zProviderSecretsListResponse_unstable, zProviderSetupCatalogListResponse_unstable, zProviderSupportedModelsListResponse_unstable, zReadResourceResponse_unstable, @@ -654,6 +662,39 @@ export class GooseExtClient { ) as ProviderConfigChangeResponse_unstable; } + async providersSecretsList_unstable( + params: ProviderSecretsListRequest_unstable, + ): Promise { + const raw = await this.conn.extMethod( + "_goose/unstable/providers/secrets/list", + params, + ); + return zProviderSecretsListResponse_unstable.parse( + raw, + ) as ProviderSecretsListResponse_unstable; + } + + async providersSecretsDelete_unstable( + params: ProviderSecretDeleteRequest_unstable, + ): Promise { + await this.conn.extMethod( + "_goose/unstable/providers/secrets/delete", + params, + ); + } + + async providersCanonicalModelInfo_unstable( + params: CanonicalModelInfoRequest_unstable, + ): Promise { + const raw = await this.conn.extMethod( + "_goose/unstable/providers/canonical-model-info", + params, + ); + return zCanonicalModelInfoResponse_unstable.parse( + raw, + ) as CanonicalModelInfoResponse_unstable; + } + async preferencesRead_unstable( params: PreferencesReadRequest_unstable, ): Promise { @@ -702,6 +743,18 @@ export class GooseExtClient { ) as DefaultsReadResponse_unstable; } + async defaultsClear_unstable( + params: DefaultsClearRequest_unstable, + ): Promise { + const raw = await this.conn.extMethod( + "_goose/unstable/defaults/clear", + params, + ); + return zDefaultsReadResponse_unstable.parse( + raw, + ) as DefaultsReadResponse_unstable; + } + async onboardingImportScan_unstable( params: OnboardingImportScanRequest_unstable, ): Promise { diff --git a/ui/sdk/src/generated/index.ts b/ui/sdk/src/generated/index.ts index d653d2b72..bfe769325 100644 --- a/ui/sdk/src/generated/index.ts +++ b/ui/sdk/src/generated/index.ts @@ -1,6 +1,6 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { AddConfigExtensionRequest_unstable, AddSessionExtensionRequest_unstable, AgentMention, Annotations, AppsExportRequest_unstable, AppsExportResponse_unstable, AppsImportRequest_unstable, AppsImportResponse_unstable, AppsListRequest_unstable, AppsListResponse_unstable, ArchiveSessionRequest_unstable, AudioContent, AvailableCommand, AvailableCommandInput, BlobResourceContents, ContentBlock, CreateScheduleRequest_unstable, CreateScheduleResponse_unstable, CreateSourceRequest_unstable, CreateSourceResponse_unstable, CustomProviderConfigDto, CustomProviderCreateRequest_unstable, CustomProviderCreateResponse_unstable, CustomProviderDeleteRequest_unstable, CustomProviderDeleteResponse_unstable, CustomProviderReadRequest_unstable, CustomProviderReadResponse_unstable, CustomProviderUpdateRequest_unstable, CustomProviderUpdateResponse_unstable, DecodeRecipeRequest_unstable, DecodeRecipeResponse_unstable, DefaultsReadRequest_unstable, DefaultsReadResponse_unstable, DefaultsSaveRequest_unstable, DeleteRecipeRequest_unstable, DeleteScheduleRequest_unstable, DeleteSessionRequest, DeleteSourceRequest_unstable, DiagnosticsGetRequest_unstable, DiagnosticsGetResponse_unstable, DiagnosticsReportLevel, DictationConfigRequest_unstable, DictationConfigResponse_unstable, DictationDownloadProgress, DictationLocalModelStatus, DictationModelCancelRequest_unstable, DictationModelDeleteRequest_unstable, DictationModelDownloadProgressRequest_unstable, DictationModelDownloadProgressResponse_unstable, DictationModelDownloadRequest_unstable, DictationModelOption, DictationModelSelectRequest_unstable, DictationModelsListRequest_unstable, DictationModelsListResponse_unstable, DictationProviderStatusEntry, DictationSecretDeleteRequest_unstable, DictationSecretSaveRequest_unstable, DictationTranscribeRequest_unstable, DictationTranscribeResponse_unstable, EmbeddedResource, EmbeddedResourceResource, EmptyResponse, EncodeRecipeRequest_unstable, EncodeRecipeResponse_unstable, EnvVariable, ExportSessionRequest_unstable, ExportSessionResponse_unstable, ExportSourceRequest_unstable, ExportSourceResponse_unstable, ExtAgentRequest, ExtAgentResponse, ExtNotification, ExtRequest, ExtResponse, GetAvailableExtensionsRequest_unstable, GetAvailableExtensionsResponse_unstable, GetConfigExtensionsRequest_unstable, GetConfigExtensionsResponse_unstable, GetPromptRequest_unstable, GetPromptResponse_unstable, GetSessionExtensionsRequest_unstable, GetSessionExtensionsResponse_unstable, GetSessionInfoRequest_unstable, GetSessionInfoResponse_unstable, GetToolsRequest_unstable, GetToolsResponse_unstable, GooseExtension, GooseExtensionEntry, GooseSessionNotification_unstable, GooseSessionUpdate, GooseToolCallRequest_unstable, GooseToolCallResponse_unstable, HttpHeader, ImageContent, ImportSessionRequest_unstable, ImportSessionResponse_unstable, ImportSourcesRequest_unstable, ImportSourcesResponse_unstable, InspectRunningJobRequest_unstable, InspectRunningJobResponse_unstable, KillRunningJobRequest_unstable, KillRunningJobResponse_unstable, ListAgentMentionsRequest_unstable, ListAgentMentionsResponse_unstable, ListPromptsRequest_unstable, ListPromptsResponse_unstable, ListProvidersRequest_unstable, ListProvidersResponse_unstable, ListRecipesRequest_unstable, ListRecipesResponse_unstable, ListScheduleSessionsRequest_unstable, ListScheduleSessionsResponse_unstable, ListSchedulesRequest_unstable, ListSchedulesResponse_unstable, ListSlashCommandsRequest_unstable, ListSlashCommandsResponse_unstable, ListSourcesRequest_unstable, ListSourcesResponse_unstable, McpServer, McpServerHttp, McpServerSse, McpServerStdio, OnboardingImportApplyRequest_unstable, OnboardingImportApplyResponse_unstable, OnboardingImportCandidate, OnboardingImportCounts, OnboardingImportScanRequest_unstable, OnboardingImportScanResponse_unstable, OnboardingImportSourceKind, ParseRecipeRequest_unstable, ParseRecipeResponse_unstable, PauseScheduleRequest_unstable, PreferenceKey, PreferencesReadRequest_unstable, PreferencesReadResponse_unstable, PreferencesRemoveRequest_unstable, PreferencesSaveRequest_unstable, PreferenceValue, PromptOperationResponse_unstable, PromptTemplateEntry, ProviderCatalogListRequest_unstable, ProviderCatalogListResponse_unstable, ProviderCatalogTemplateRequest_unstable, ProviderCatalogTemplateResponse_unstable, ProviderConfigAuthenticateRequest_unstable, ProviderConfigChangeResponse_unstable, ProviderConfigDeleteRequest_unstable, ProviderConfigFieldUpdate, ProviderConfigFieldValueDto, ProviderConfigKey, ProviderConfigReadRequest_unstable, ProviderConfigReadResponse_unstable, ProviderConfigSaveRequest_unstable, ProviderConfigStatusDto, ProviderConfigStatusRequest_unstable, ProviderConfigStatusResponse_unstable, ProviderInventoryEntryDto, ProviderInventoryModelDto, ProviderSetupCatalogEntryDto, ProviderSetupCatalogListRequest_unstable, ProviderSetupCatalogListResponse_unstable, ProviderSetupCategoryDto, ProviderSetupFieldDto, ProviderSetupGroupDto, ProviderSetupMethodDto, ProviderSupportedModelsListRequest_unstable, ProviderSupportedModelsListResponse_unstable, ProviderTemplateCapabilitiesDto, ProviderTemplateCatalogEntryDto, ProviderTemplateDto, ProviderTemplateModelDto, ReadResourceRequest_unstable, ReadResourceResponse_unstable, RecipeAuthorDto, RecipeDto, RecipeExtensionDto, RecipeListEntryDto, RecipeParameterDto, RecipeParameterInputTypeDto, RecipeParameterRequirementDto, RecipeParamsAction, RecipeParamsResponse_unstable, RecipeResponseDto, RecipeRetryConfigDto, RecipeSettingsDto, RecipeSuccessCheckDto, RecipeToYamlRequest_unstable, RecipeToYamlResponse_unstable, RefreshProviderInventoryRequest_unstable, RefreshProviderInventoryResponse_unstable, RefreshProviderInventorySkipDto, RefreshProviderInventorySkipReasonDto, RemoveConfigExtensionRequest_unstable, RemoveSessionExtensionRequest_unstable, RenameSessionRequest_unstable, RequestRecipeParams_unstable, ResetPromptRequest_unstable, ResourceLink, Role, RunScheduleNowRequest_unstable, RunScheduleNowResponse_unstable, RunScheduleNowStatus, SavePromptRequest_unstable, SaveRecipeRequest_unstable, SaveRecipeResponse_unstable, ScanRecipeRequest_unstable, ScanRecipeResponse_unstable, ScheduledJobDto, ScheduleRecipeRequest_unstable, SessionId, SessionImportSource, SessionInfo, SessionSystemPromptMode, SessionUsageUpdate, SetConfigExtensionEnabledRequest_unstable, SetRecipeSlashCommandRequest_unstable, SetSessionSystemPromptRequest_unstable, SetToolPermissionsRequest_unstable, SetToolPermissionsResponse_unstable, ShareSessionNostrRequest_unstable, ShareSessionNostrResponse_unstable, SourceEntry, SourceScope, SourceType, StatusMessage, StatusMessageUpdate, SteerSessionRequest_unstable, SteerSessionResponse_unstable, SubRecipeDto, TextContent, TextResourceContents, ToolListItem, ToolPermissionEntry, ToolPermissionLevel, TruncateSessionConversationRequest_unstable, UnarchiveSessionRequest_unstable, UnpauseScheduleRequest_unstable, UnstructuredCommandInput, UpdateScheduleRequest_unstable, UpdateScheduleResponse_unstable, UpdateSessionProjectRequest_unstable, UpdateSourceRequest_unstable, UpdateSourceResponse_unstable, UpdateWorkingDirRequest_unstable } from './types.gen.js'; +export type { AddConfigExtensionRequest_unstable, AddSessionExtensionRequest_unstable, AgentMention, Annotations, AppsExportRequest_unstable, AppsExportResponse_unstable, AppsImportRequest_unstable, AppsImportResponse_unstable, AppsListRequest_unstable, AppsListResponse_unstable, ArchiveSessionRequest_unstable, AudioContent, AvailableCommand, AvailableCommandInput, BlobResourceContents, CanonicalModelInfoDto, CanonicalModelInfoRequest_unstable, CanonicalModelInfoResponse_unstable, ContentBlock, CreateScheduleRequest_unstable, CreateScheduleResponse_unstable, CreateSourceRequest_unstable, CreateSourceResponse_unstable, CustomProviderConfigDto, CustomProviderCreateRequest_unstable, CustomProviderCreateResponse_unstable, CustomProviderDeleteRequest_unstable, CustomProviderDeleteResponse_unstable, CustomProviderReadRequest_unstable, CustomProviderReadResponse_unstable, CustomProviderUpdateRequest_unstable, CustomProviderUpdateResponse_unstable, DecodeRecipeRequest_unstable, DecodeRecipeResponse_unstable, DefaultsClearRequest_unstable, DefaultsReadRequest_unstable, DefaultsReadResponse_unstable, DefaultsSaveRequest_unstable, DeleteRecipeRequest_unstable, DeleteScheduleRequest_unstable, DeleteSessionRequest, DeleteSourceRequest_unstable, DiagnosticsGetRequest_unstable, DiagnosticsGetResponse_unstable, DiagnosticsReportLevel, DictationConfigRequest_unstable, DictationConfigResponse_unstable, DictationDownloadProgress, DictationLocalModelStatus, DictationModelCancelRequest_unstable, DictationModelDeleteRequest_unstable, DictationModelDownloadProgressRequest_unstable, DictationModelDownloadProgressResponse_unstable, DictationModelDownloadRequest_unstable, DictationModelOption, DictationModelSelectRequest_unstable, DictationModelsListRequest_unstable, DictationModelsListResponse_unstable, DictationProviderStatusEntry, DictationSecretDeleteRequest_unstable, DictationSecretSaveRequest_unstable, DictationTranscribeRequest_unstable, DictationTranscribeResponse_unstable, EmbeddedResource, EmbeddedResourceResource, EmptyResponse, EncodeRecipeRequest_unstable, EncodeRecipeResponse_unstable, EnvVariable, ExportSessionRequest_unstable, ExportSessionResponse_unstable, ExportSourceRequest_unstable, ExportSourceResponse_unstable, ExtAgentRequest, ExtAgentResponse, ExtNotification, ExtRequest, ExtResponse, GetAvailableExtensionsRequest_unstable, GetAvailableExtensionsResponse_unstable, GetConfigExtensionsRequest_unstable, GetConfigExtensionsResponse_unstable, GetPromptRequest_unstable, GetPromptResponse_unstable, GetSessionExtensionsRequest_unstable, GetSessionExtensionsResponse_unstable, GetSessionInfoRequest_unstable, GetSessionInfoResponse_unstable, GetToolsRequest_unstable, GetToolsResponse_unstable, GooseExtension, GooseExtensionEntry, GooseSessionNotification_unstable, GooseSessionUpdate, GooseToolCallRequest_unstable, GooseToolCallResponse_unstable, HttpHeader, ImageContent, ImportSessionRequest_unstable, ImportSessionResponse_unstable, ImportSourcesRequest_unstable, ImportSourcesResponse_unstable, InspectRunningJobRequest_unstable, InspectRunningJobResponse_unstable, KillRunningJobRequest_unstable, KillRunningJobResponse_unstable, ListAgentMentionsRequest_unstable, ListAgentMentionsResponse_unstable, ListPromptsRequest_unstable, ListPromptsResponse_unstable, ListProvidersRequest_unstable, ListProvidersResponse_unstable, ListRecipesRequest_unstable, ListRecipesResponse_unstable, ListScheduleSessionsRequest_unstable, ListScheduleSessionsResponse_unstable, ListSchedulesRequest_unstable, ListSchedulesResponse_unstable, ListSlashCommandsRequest_unstable, ListSlashCommandsResponse_unstable, ListSourcesRequest_unstable, ListSourcesResponse_unstable, McpServer, McpServerHttp, McpServerSse, McpServerStdio, OnboardingImportApplyRequest_unstable, OnboardingImportApplyResponse_unstable, OnboardingImportCandidate, OnboardingImportCounts, OnboardingImportScanRequest_unstable, OnboardingImportScanResponse_unstable, OnboardingImportSourceKind, ParseRecipeRequest_unstable, ParseRecipeResponse_unstable, PauseScheduleRequest_unstable, PreferenceKey, PreferencesReadRequest_unstable, PreferencesReadResponse_unstable, PreferencesRemoveRequest_unstable, PreferencesSaveRequest_unstable, PreferenceValue, PromptOperationResponse_unstable, PromptTemplateEntry, ProviderCatalogListRequest_unstable, ProviderCatalogListResponse_unstable, ProviderCatalogTemplateRequest_unstable, ProviderCatalogTemplateResponse_unstable, ProviderConfigAuthenticateRequest_unstable, ProviderConfigChangeResponse_unstable, ProviderConfigDeleteRequest_unstable, ProviderConfigFieldUpdate, ProviderConfigFieldValueDto, ProviderConfigKey, ProviderConfigReadRequest_unstable, ProviderConfigReadResponse_unstable, ProviderConfigSaveRequest_unstable, ProviderConfigStatusDto, ProviderConfigStatusRequest_unstable, ProviderConfigStatusResponse_unstable, ProviderInventoryEntryDto, ProviderInventoryModelDto, ProviderSecretDeleteRequest_unstable, ProviderSecretDto, ProviderSecretsListRequest_unstable, ProviderSecretsListResponse_unstable, ProviderSecretStatusDto, ProviderSecretStorageDto, ProviderSetupCatalogEntryDto, ProviderSetupCatalogListRequest_unstable, ProviderSetupCatalogListResponse_unstable, ProviderSetupCategoryDto, ProviderSetupFieldDto, ProviderSetupGroupDto, ProviderSetupMethodDto, ProviderSupportedModelsListRequest_unstable, ProviderSupportedModelsListResponse_unstable, ProviderTemplateCapabilitiesDto, ProviderTemplateCatalogEntryDto, ProviderTemplateDto, ProviderTemplateModelDto, ReadResourceRequest_unstable, ReadResourceResponse_unstable, RecipeAuthorDto, RecipeDto, RecipeExtensionDto, RecipeListEntryDto, RecipeParameterDto, RecipeParameterInputTypeDto, RecipeParameterRequirementDto, RecipeParamsAction, RecipeParamsResponse_unstable, RecipeResponseDto, RecipeRetryConfigDto, RecipeSettingsDto, RecipeSuccessCheckDto, RecipeToYamlRequest_unstable, RecipeToYamlResponse_unstable, RefreshProviderInventoryRequest_unstable, RefreshProviderInventoryResponse_unstable, RefreshProviderInventorySkipDto, RefreshProviderInventorySkipReasonDto, RemoveConfigExtensionRequest_unstable, RemoveSessionExtensionRequest_unstable, RenameSessionRequest_unstable, RequestRecipeParams_unstable, ResetPromptRequest_unstable, ResourceLink, Role, RunScheduleNowRequest_unstable, RunScheduleNowResponse_unstable, RunScheduleNowStatus, SavePromptRequest_unstable, SaveRecipeRequest_unstable, SaveRecipeResponse_unstable, ScanRecipeRequest_unstable, ScanRecipeResponse_unstable, ScheduledJobDto, ScheduleRecipeRequest_unstable, SessionId, SessionImportSource, SessionInfo, SessionSystemPromptMode, SessionUsageUpdate, SetConfigExtensionEnabledRequest_unstable, SetRecipeSlashCommandRequest_unstable, SetSessionSystemPromptRequest_unstable, SetToolPermissionsRequest_unstable, SetToolPermissionsResponse_unstable, ShareSessionNostrRequest_unstable, ShareSessionNostrResponse_unstable, SourceEntry, SourceScope, SourceType, StatusMessage, StatusMessageUpdate, SteerSessionRequest_unstable, SteerSessionResponse_unstable, SubRecipeDto, TextContent, TextResourceContents, ToolListItem, ToolPermissionEntry, ToolPermissionLevel, TruncateSessionConversationRequest_unstable, UnarchiveSessionRequest_unstable, UnpauseScheduleRequest_unstable, UnstructuredCommandInput, UpdateScheduleRequest_unstable, UpdateScheduleResponse_unstable, UpdateSessionProjectRequest_unstable, UpdateSourceRequest_unstable, UpdateSourceResponse_unstable, UpdateWorkingDirRequest_unstable } from './types.gen.js'; export const GOOSE_EXT_METHODS = [ { @@ -198,6 +198,21 @@ export const GOOSE_EXT_METHODS = [ requestType: "ProviderConfigAuthenticateRequest_unstable", responseType: "ProviderConfigChangeResponse_unstable", }, + { + method: "_goose/unstable/providers/secrets/list", + requestType: "ProviderSecretsListRequest_unstable", + responseType: "ProviderSecretsListResponse_unstable", + }, + { + method: "_goose/unstable/providers/secrets/delete", + requestType: "ProviderSecretDeleteRequest_unstable", + responseType: "EmptyResponse", + }, + { + method: "_goose/unstable/providers/canonical-model-info", + requestType: "CanonicalModelInfoRequest_unstable", + responseType: "CanonicalModelInfoResponse_unstable", + }, { method: "_goose/unstable/preferences/read", requestType: "PreferencesReadRequest_unstable", @@ -223,6 +238,11 @@ export const GOOSE_EXT_METHODS = [ requestType: "DefaultsSaveRequest_unstable", responseType: "DefaultsReadResponse_unstable", }, + { + method: "_goose/unstable/defaults/clear", + requestType: "DefaultsClearRequest_unstable", + responseType: "DefaultsReadResponse_unstable", + }, { method: "_goose/unstable/onboarding/import/scan", requestType: "OnboardingImportScanRequest_unstable", diff --git a/ui/sdk/src/generated/types.gen.ts b/ui/sdk/src/generated/types.gen.ts index c3e04eb1c..864ced01b 100644 --- a/ui/sdk/src/generated/types.gen.ts +++ b/ui/sdk/src/generated/types.gen.ts @@ -1124,6 +1124,68 @@ export type ProviderConfigAuthenticateRequest_unstable = { providerId: string; }; +/** + * List provider credentials stored locally by Goose. + */ +export type ProviderSecretsListRequest_unstable = { + [key: string]: unknown; +}; + +export type ProviderSecretsListResponse_unstable = { + secrets: Array; +}; + +export type ProviderSecretDto = { + id: string; + provider: string; + providerDisplayName: string; + name: string; + storage: ProviderSecretStorageDto; + expiresAt?: string | null; + status: ProviderSecretStatusDto; + configured: boolean; + hasSecret: boolean; + canDelete: boolean; + canConfigure: boolean; + configureProvider?: string | null; +}; + +export type ProviderSecretStorageDto = 'secret_store' | 'provider_cache'; + +export type ProviderSecretStatusDto = 'valid' | 'expired' | 'unknown'; + +/** + * Delete a locally stored provider credential by id. + */ +export type ProviderSecretDeleteRequest_unstable = { + id: string; +}; + +/** + * Look up canonical (bundled-registry) model info for a provider/model pair. + */ +export type CanonicalModelInfoRequest_unstable = { + provider: string; + model: string; +}; + +export type CanonicalModelInfoResponse_unstable = { + modelInfo?: CanonicalModelInfoDto | null; +}; + +export type CanonicalModelInfoDto = { + provider: string; + model: string; + contextLimit: number; + maxOutputTokens?: number | null; + reasoning: boolean; + inputTokenCost?: number | null; + outputTokenCost?: number | null; + cacheReadTokenCost?: number | null; + cacheWriteTokenCost?: number | null; + currency: string; +}; + /** * Read allowlisted user preferences. Empty `keys` means all supported preferences. */ @@ -1176,6 +1238,13 @@ export type DefaultsSaveRequest_unstable = { modelId?: string | null; }; +/** + * Clear Goose default provider and model configuration. + */ +export type DefaultsClearRequest_unstable = { + [key: string]: unknown; +}; + /** * Scan for existing Goose and compatible app data that onboarding can import. */ @@ -2126,14 +2195,14 @@ export type RecipeParamsAction = 'submit' | 'cancel'; export type ExtRequest = { id: string; method: string; - params?: AddSessionExtensionRequest_unstable | RemoveSessionExtensionRequest_unstable | GetToolsRequest_unstable | SetToolPermissionsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | AppsListRequest_unstable | AppsExportRequest_unstable | AppsImportRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | SteerSessionRequest_unstable | DiagnosticsGetRequest_unstable | ListPromptsRequest_unstable | GetPromptRequest_unstable | SavePromptRequest_unstable | ResetPromptRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | ShareSessionNostrRequest_unstable | EncodeRecipeRequest_unstable | DecodeRecipeRequest_unstable | ScanRecipeRequest_unstable | ListRecipesRequest_unstable | DeleteRecipeRequest_unstable | ScheduleRecipeRequest_unstable | SetRecipeSlashCommandRequest_unstable | SaveRecipeRequest_unstable | ParseRecipeRequest_unstable | RecipeToYamlRequest_unstable | ListSchedulesRequest_unstable | ListScheduleSessionsRequest_unstable | CreateScheduleRequest_unstable | DeleteScheduleRequest_unstable | PauseScheduleRequest_unstable | UnpauseScheduleRequest_unstable | UpdateScheduleRequest_unstable | RunScheduleNowRequest_unstable | KillRunningJobRequest_unstable | InspectRunningJobRequest_unstable | GetSessionInfoRequest_unstable | TruncateSessionConversationRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | ListAgentMentionsRequest_unstable | ListSlashCommandsRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | { + params?: AddSessionExtensionRequest_unstable | RemoveSessionExtensionRequest_unstable | GetToolsRequest_unstable | SetToolPermissionsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | AppsListRequest_unstable | AppsExportRequest_unstable | AppsImportRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | SteerSessionRequest_unstable | DiagnosticsGetRequest_unstable | ListPromptsRequest_unstable | GetPromptRequest_unstable | SavePromptRequest_unstable | ResetPromptRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | ProviderSecretsListRequest_unstable | ProviderSecretDeleteRequest_unstable | CanonicalModelInfoRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | DefaultsClearRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | ShareSessionNostrRequest_unstable | EncodeRecipeRequest_unstable | DecodeRecipeRequest_unstable | ScanRecipeRequest_unstable | ListRecipesRequest_unstable | DeleteRecipeRequest_unstable | ScheduleRecipeRequest_unstable | SetRecipeSlashCommandRequest_unstable | SaveRecipeRequest_unstable | ParseRecipeRequest_unstable | RecipeToYamlRequest_unstable | ListSchedulesRequest_unstable | ListScheduleSessionsRequest_unstable | CreateScheduleRequest_unstable | DeleteScheduleRequest_unstable | PauseScheduleRequest_unstable | UnpauseScheduleRequest_unstable | UpdateScheduleRequest_unstable | RunScheduleNowRequest_unstable | KillRunningJobRequest_unstable | InspectRunningJobRequest_unstable | GetSessionInfoRequest_unstable | TruncateSessionConversationRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | ListAgentMentionsRequest_unstable | ListSlashCommandsRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | { [key: string]: unknown; } | null; }; export type ExtResponse = { id: string; - result?: EmptyResponse | GetToolsResponse_unstable | SetToolPermissionsResponse_unstable | GooseToolCallResponse_unstable | ReadResourceResponse_unstable | AppsListResponse_unstable | AppsExportResponse_unstable | AppsImportResponse_unstable | SteerSessionResponse_unstable | DiagnosticsGetResponse_unstable | ListPromptsResponse_unstable | GetPromptResponse_unstable | PromptOperationResponse_unstable | GetConfigExtensionsResponse_unstable | GetAvailableExtensionsResponse_unstable | GetSessionExtensionsResponse_unstable | ListProvidersResponse_unstable | ProviderSupportedModelsListResponse_unstable | ProviderCatalogListResponse_unstable | ProviderSetupCatalogListResponse_unstable | ProviderCatalogTemplateResponse_unstable | CustomProviderCreateResponse_unstable | CustomProviderReadResponse_unstable | CustomProviderUpdateResponse_unstable | CustomProviderDeleteResponse_unstable | RefreshProviderInventoryResponse_unstable | ProviderConfigReadResponse_unstable | ProviderConfigStatusResponse_unstable | ProviderConfigChangeResponse_unstable | PreferencesReadResponse_unstable | DefaultsReadResponse_unstable | OnboardingImportScanResponse_unstable | OnboardingImportApplyResponse_unstable | ExportSessionResponse_unstable | ImportSessionResponse_unstable | ShareSessionNostrResponse_unstable | EncodeRecipeResponse_unstable | DecodeRecipeResponse_unstable | ScanRecipeResponse_unstable | ListRecipesResponse_unstable | SaveRecipeResponse_unstable | ParseRecipeResponse_unstable | RecipeToYamlResponse_unstable | ListSchedulesResponse_unstable | ListScheduleSessionsResponse_unstable | CreateScheduleResponse_unstable | UpdateScheduleResponse_unstable | RunScheduleNowResponse_unstable | KillRunningJobResponse_unstable | InspectRunningJobResponse_unstable | GetSessionInfoResponse_unstable | CreateSourceResponse_unstable | ListSourcesResponse_unstable | ListAgentMentionsResponse_unstable | ListSlashCommandsResponse_unstable | UpdateSourceResponse_unstable | ExportSourceResponse_unstable | ImportSourcesResponse_unstable | DictationTranscribeResponse_unstable | DictationConfigResponse_unstable | DictationModelsListResponse_unstable | DictationModelDownloadProgressResponse_unstable | unknown; + result?: EmptyResponse | GetToolsResponse_unstable | SetToolPermissionsResponse_unstable | GooseToolCallResponse_unstable | ReadResourceResponse_unstable | AppsListResponse_unstable | AppsExportResponse_unstable | AppsImportResponse_unstable | SteerSessionResponse_unstable | DiagnosticsGetResponse_unstable | ListPromptsResponse_unstable | GetPromptResponse_unstable | PromptOperationResponse_unstable | GetConfigExtensionsResponse_unstable | GetAvailableExtensionsResponse_unstable | GetSessionExtensionsResponse_unstable | ListProvidersResponse_unstable | ProviderSupportedModelsListResponse_unstable | ProviderCatalogListResponse_unstable | ProviderSetupCatalogListResponse_unstable | ProviderCatalogTemplateResponse_unstable | CustomProviderCreateResponse_unstable | CustomProviderReadResponse_unstable | CustomProviderUpdateResponse_unstable | CustomProviderDeleteResponse_unstable | RefreshProviderInventoryResponse_unstable | ProviderConfigReadResponse_unstable | ProviderConfigStatusResponse_unstable | ProviderConfigChangeResponse_unstable | ProviderSecretsListResponse_unstable | CanonicalModelInfoResponse_unstable | PreferencesReadResponse_unstable | DefaultsReadResponse_unstable | OnboardingImportScanResponse_unstable | OnboardingImportApplyResponse_unstable | ExportSessionResponse_unstable | ImportSessionResponse_unstable | ShareSessionNostrResponse_unstable | EncodeRecipeResponse_unstable | DecodeRecipeResponse_unstable | ScanRecipeResponse_unstable | ListRecipesResponse_unstable | SaveRecipeResponse_unstable | ParseRecipeResponse_unstable | RecipeToYamlResponse_unstable | ListSchedulesResponse_unstable | ListScheduleSessionsResponse_unstable | CreateScheduleResponse_unstable | UpdateScheduleResponse_unstable | RunScheduleNowResponse_unstable | KillRunningJobResponse_unstable | InspectRunningJobResponse_unstable | GetSessionInfoResponse_unstable | CreateSourceResponse_unstable | ListSourcesResponse_unstable | ListAgentMentionsResponse_unstable | ListSlashCommandsResponse_unstable | UpdateSourceResponse_unstable | ExportSourceResponse_unstable | ImportSourcesResponse_unstable | DictationTranscribeResponse_unstable | DictationConfigResponse_unstable | DictationModelsListResponse_unstable | DictationModelDownloadProgressResponse_unstable | unknown; } | { error: { code: number; diff --git a/ui/sdk/src/generated/zod.gen.ts b/ui/sdk/src/generated/zod.gen.ts index 3f2639f3b..4ffed9a6d 100644 --- a/ui/sdk/src/generated/zod.gen.ts +++ b/ui/sdk/src/generated/zod.gen.ts @@ -8,7 +8,10 @@ import { z } from 'zod'; export const zHttpHeader = z.object({ name: z.string(), value: z.string(), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** @@ -18,7 +21,10 @@ export const zMcpServerHttp = z.object({ name: z.string(), url: z.string(), headers: z.array(zHttpHeader), - _meta: z.record(z.unknown()).nullish(), + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional(), type: z.literal('http') }); @@ -29,7 +35,10 @@ export const zMcpServerSse = z.object({ name: z.string(), url: z.string(), headers: z.array(zHttpHeader), - _meta: z.record(z.unknown()).nullish(), + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional(), type: z.literal('sse') }); @@ -39,7 +48,10 @@ export const zMcpServerSse = z.object({ export const zEnvVariable = z.object({ name: z.string(), value: z.string(), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** @@ -50,7 +62,10 @@ export const zMcpServerStdio = z.object({ command: z.string(), args: z.array(z.string()), env: z.array(zEnvVariable), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** @@ -70,26 +85,59 @@ export const zMcpServer = z.union([ export const zGooseExtension = z.union([ z.object({ name: z.string(), - description: z.string().nullish(), - display_name: z.string().nullish(), - timeout: z.number().int().gte(0).nullish(), - bundled: z.boolean().nullish(), + description: z.union([ + z.string(), + z.null() + ]).optional(), + display_name: z.union([ + z.string(), + z.null() + ]).optional(), + timeout: z.union([ + z.number().int().gte(0), + z.null() + ]).optional(), + bundled: z.union([ + z.boolean(), + z.null() + ]).optional(), type: z.literal('builtin') }), z.object({ name: z.string(), - description: z.string().nullish(), - display_name: z.string().nullish(), - bundled: z.boolean().nullish(), + description: z.union([ + z.string(), + z.null() + ]).optional(), + display_name: z.union([ + z.string(), + z.null() + ]).optional(), + bundled: z.union([ + z.boolean(), + z.null() + ]).optional(), type: z.literal('platform') }), z.object({ server: zMcpServer, envKeys: z.array(z.string()).optional(), - description: z.string().nullish(), - timeout: z.number().int().gte(0).nullish(), - socket: z.string().nullish(), - bundled: z.boolean().nullish(), + description: z.union([ + z.string(), + z.null() + ]).optional(), + timeout: z.union([ + z.number().int().gte(0), + z.null() + ]).optional(), + socket: z.union([ + z.string(), + z.null() + ]).optional(), + bundled: z.union([ + z.boolean(), + z.null() + ]).optional(), type: z.literal('mcp') }) ]); @@ -120,7 +168,10 @@ export const zRemoveSessionExtensionRequest_unstable = z.object({ */ export const zGetToolsRequest_unstable = z.object({ sessionId: z.string(), - extensionName: z.string().nullish() + extensionName: z.union([ + z.string(), + z.null() + ]).optional() }); /** @@ -139,7 +190,10 @@ export const zToolListItem = z.object({ name: z.string(), description: z.string(), parameters: z.array(z.string()), - permission: zToolPermissionLevel.nullish(), + permission: z.union([ + zToolPermissionLevel, + z.null() + ]).optional(), inputSchema: z.unknown(), outputSchema: z.unknown().optional() }); @@ -204,7 +258,10 @@ export const zReadResourceResponse_unstable = z.object({ }); export const zAppsListRequest_unstable = z.object({ - sessionId: z.string().nullish() + sessionId: z.union([ + z.string(), + z.null() + ]).optional() }); export const zAppsListResponse_unstable = z.object({ @@ -254,7 +311,10 @@ export const zSessionSystemPromptMode = z.union([ export const zSetSessionSystemPromptRequest_unstable = z.object({ sessionId: z.string(), mode: zSessionSystemPromptMode.optional().default('append'), - key: z.string().nullish(), + key: z.union([ + z.string(), + z.null() + ]).optional(), text: z.string() }); @@ -267,64 +327,121 @@ export const zRole = z.enum(['assistant', 'user']); * Optional annotations for the client. The client can use annotations to inform how objects are used or displayed */ export const zAnnotations = z.object({ - audience: z.array(zRole).nullish(), - lastModified: z.string().nullish(), - priority: z.number().nullish(), - _meta: z.record(z.unknown()).nullish() + audience: z.union([ + z.array(zRole), + z.null() + ]).optional(), + lastModified: z.union([ + z.string(), + z.null() + ]).optional(), + priority: z.union([ + z.number(), + z.null() + ]).optional(), + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** * Text provided to or from an LLM. */ export const zTextContent = z.object({ - annotations: zAnnotations.nullish(), + annotations: z.union([ + zAnnotations, + z.null() + ]).optional(), text: z.string(), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** * An image provided to or from an LLM. */ export const zImageContent = z.object({ - annotations: zAnnotations.nullish(), + annotations: z.union([ + zAnnotations, + z.null() + ]).optional(), data: z.string(), mimeType: z.string(), - uri: z.string().nullish(), - _meta: z.record(z.unknown()).nullish() + uri: z.union([ + z.string(), + z.null() + ]).optional(), + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** * Audio provided to or from an LLM. */ export const zAudioContent = z.object({ - annotations: zAnnotations.nullish(), + annotations: z.union([ + zAnnotations, + z.null() + ]).optional(), data: z.string(), mimeType: z.string(), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** * A resource that the server is capable of reading, included in a prompt or tool call result. */ export const zResourceLink = z.object({ - annotations: zAnnotations.nullish(), - description: z.string().nullish(), - mimeType: z.string().nullish(), + annotations: z.union([ + zAnnotations, + z.null() + ]).optional(), + description: z.union([ + z.string(), + z.null() + ]).optional(), + mimeType: z.union([ + z.string(), + z.null() + ]).optional(), name: z.string(), - size: z.number().int().nullish(), - title: z.string().nullish(), + size: z.union([ + z.number().int(), + z.null() + ]).optional(), + title: z.union([ + z.string(), + z.null() + ]).optional(), uri: z.string(), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** * Text-based resource contents. */ export const zTextResourceContents = z.object({ - mimeType: z.string().nullish(), + mimeType: z.union([ + z.string(), + z.null() + ]).optional(), text: z.string(), uri: z.string(), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** @@ -332,9 +449,15 @@ export const zTextResourceContents = z.object({ */ export const zBlobResourceContents = z.object({ blob: z.string(), - mimeType: z.string().nullish(), + mimeType: z.union([ + z.string(), + z.null() + ]).optional(), uri: z.string(), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** @@ -349,9 +472,15 @@ export const zEmbeddedResourceResource = z.union([ * The contents of a resource, embedded into a prompt or tool call result. */ export const zEmbeddedResource = z.object({ - annotations: zAnnotations.nullish(), + annotations: z.union([ + zAnnotations, + z.null() + ]).optional(), resource: zEmbeddedResourceResource, - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** @@ -425,7 +554,10 @@ export const zPromptTemplateEntry = z.object({ name: z.string(), description: z.string(), defaultContent: z.string(), - userContent: z.string().nullish(), + userContent: z.union([ + z.string(), + z.null() + ]).optional(), isCustomized: z.boolean() }); @@ -481,7 +613,10 @@ export const zGetConfigExtensionsRequest_unstable = z.record(z.unknown()); export const zGooseExtensionEntry = z.object({ extension: zGooseExtension, enabled: z.boolean(), - configKey: z.string().nullish() + configKey: z.union([ + z.string(), + z.null() + ]).optional() }); /** @@ -545,7 +680,10 @@ export const zProviderConfigKey = z.object({ name: z.string(), required: z.boolean(), secret: z.boolean(), - default: z.string().nullish().default(null), + default: z.union([ + z.string(), + z.null() + ]).optional().default(null), oauthFlow: z.boolean().optional().default(false), deviceCodeFlow: z.boolean().optional().default(false), primary: z.boolean().optional().default(false) @@ -557,9 +695,18 @@ export const zProviderConfigKey = z.object({ export const zProviderInventoryModelDto = z.object({ id: z.string(), name: z.string(), - family: z.string().nullish(), - contextLimit: z.number().int().gte(0).nullish(), - reasoning: z.boolean().nullish(), + family: z.union([ + z.string(), + z.null() + ]).optional(), + contextLimit: z.union([ + z.number().int().gte(0), + z.null() + ]).optional(), + reasoning: z.union([ + z.boolean(), + z.null() + ]).optional(), recommended: z.boolean().optional().default(false) }); @@ -579,11 +726,23 @@ export const zProviderInventoryEntryDto = z.object({ supportsRefresh: z.boolean(), refreshing: z.boolean(), models: z.array(zProviderInventoryModelDto), - lastUpdatedAt: z.string().nullish(), - lastRefreshAttemptAt: z.string().nullish(), - lastRefreshError: z.string().nullish(), + lastUpdatedAt: z.union([ + z.string(), + z.null() + ]).optional(), + lastRefreshAttemptAt: z.union([ + z.string(), + z.null() + ]).optional(), + lastRefreshError: z.union([ + z.string(), + z.null() + ]).optional(), stale: z.boolean(), - modelSelectionHint: z.string().nullish() + modelSelectionHint: z.union([ + z.string(), + z.null() + ]).optional() }); /** @@ -609,7 +768,10 @@ export const zProviderSupportedModelsListResponse_unstable = z.object({ * List custom-provider catalog entries. Omit `format` to list all formats. */ export const zProviderCatalogListRequest_unstable = z.object({ - format: z.string().nullish() + format: z.union([ + z.string(), + z.null() + ]).optional() }); export const zProviderTemplateCatalogEntryDto = z.object({ @@ -648,8 +810,14 @@ export const zProviderSetupFieldDto = z.object({ label: z.string(), secret: z.boolean(), required: z.boolean(), - placeholder: z.string().nullish(), - defaultValue: z.string().nullish() + placeholder: z.union([ + z.string(), + z.null() + ]).optional(), + defaultValue: z.union([ + z.string(), + z.null() + ]).optional() }); export const zProviderSetupGroupDto = z.enum(['default', 'additional']); @@ -660,10 +828,19 @@ export const zProviderSetupCatalogEntryDto = z.object({ category: zProviderSetupCategoryDto, description: z.string(), setupMethod: zProviderSetupMethodDto, - nativeConnectQuery: z.string().nullish(), + nativeConnectQuery: z.union([ + z.string(), + z.null() + ]).optional(), fields: z.array(zProviderSetupFieldDto).optional().default([]), - binaryName: z.string().nullish(), - docUrl: z.string().nullish(), + binaryName: z.union([ + z.string(), + z.null() + ]).optional(), + docUrl: z.union([ + z.string(), + z.null() + ]).optional(), group: zProviderSetupGroupDto, showOnlyWhenInstalled: z.boolean(), aliases: z.array(z.string()).optional().default([]), @@ -720,14 +897,29 @@ export const zCustomProviderCreateRequest_unstable = z.object({ engine: z.string(), displayName: z.string(), apiUrl: z.string(), - apiKey: z.string().nullish(), + apiKey: z.union([ + z.string(), + z.null() + ]).optional(), models: z.array(z.string()).optional().default([]), - supportsStreaming: z.boolean().nullish(), + supportsStreaming: z.union([ + z.boolean(), + z.null() + ]).optional(), headers: z.record(z.string()).optional().default({}), requiresAuth: z.boolean(), - catalogProviderId: z.string().nullish(), - basePath: z.string().nullish(), - preservesThinking: z.boolean().nullish() + catalogProviderId: z.union([ + z.string(), + z.null() + ]).optional(), + basePath: z.union([ + z.string(), + z.null() + ]).optional(), + preservesThinking: z.union([ + z.boolean(), + z.null() + ]).optional() }); export const zProviderConfigStatusDto = z.object({ @@ -774,12 +966,24 @@ export const zCustomProviderConfigDto = z.object({ displayName: z.string(), apiUrl: z.string(), models: z.array(z.string()).optional().default([]), - supportsStreaming: z.boolean().nullish(), + supportsStreaming: z.union([ + z.boolean(), + z.null() + ]).optional(), headers: z.record(z.string()).optional().default({}), requiresAuth: z.boolean(), - catalogProviderId: z.string().nullish(), - basePath: z.string().nullish(), - apiKeyEnv: z.string().nullish(), + catalogProviderId: z.union([ + z.string(), + z.null() + ]).optional(), + basePath: z.union([ + z.string(), + z.null() + ]).optional(), + apiKeyEnv: z.union([ + z.string(), + z.null() + ]).optional(), apiKeySet: z.boolean(), preservesThinking: z.boolean() }); @@ -798,14 +1002,29 @@ export const zCustomProviderUpdateRequest_unstable = z.object({ engine: z.string(), displayName: z.string(), apiUrl: z.string(), - apiKey: z.string().nullish(), + apiKey: z.union([ + z.string(), + z.null() + ]).optional(), models: z.array(z.string()).optional().default([]), - supportsStreaming: z.boolean().nullish(), + supportsStreaming: z.union([ + z.boolean(), + z.null() + ]).optional(), headers: z.record(z.string()).optional().default({}), requiresAuth: z.boolean(), - catalogProviderId: z.string().nullish(), - basePath: z.string().nullish(), - preservesThinking: z.boolean().nullish() + catalogProviderId: z.union([ + z.string(), + z.null() + ]).optional(), + basePath: z.union([ + z.string(), + z.null() + ]).optional(), + preservesThinking: z.union([ + z.boolean(), + z.null() + ]).optional() }); export const zCustomProviderUpdateResponse_unstable = z.object({ @@ -842,7 +1061,10 @@ export const zProviderConfigReadRequest_unstable = z.object({ export const zProviderConfigFieldValueDto = z.object({ key: z.string(), - value: z.string().nullish().default(null), + value: z.union([ + z.string(), + z.null() + ]).optional().default(null), isSet: z.boolean(), isSecret: z.boolean(), required: z.boolean() @@ -895,6 +1117,94 @@ export const zProviderConfigAuthenticateRequest_unstable = z.object({ providerId: z.string() }); +/** + * List provider credentials stored locally by Goose. + */ +export const zProviderSecretsListRequest_unstable = z.record(z.unknown()); + +export const zProviderSecretStorageDto = z.enum(['secret_store', 'provider_cache']); + +export const zProviderSecretStatusDto = z.enum([ + 'valid', + 'expired', + 'unknown' +]); + +export const zProviderSecretDto = z.object({ + id: z.string(), + provider: z.string(), + providerDisplayName: z.string(), + name: z.string(), + storage: zProviderSecretStorageDto, + expiresAt: z.union([ + z.string(), + z.null() + ]).optional(), + status: zProviderSecretStatusDto, + configured: z.boolean(), + hasSecret: z.boolean(), + canDelete: z.boolean(), + canConfigure: z.boolean(), + configureProvider: z.union([ + z.string(), + z.null() + ]).optional() +}); + +export const zProviderSecretsListResponse_unstable = z.object({ + secrets: z.array(zProviderSecretDto) +}); + +/** + * Delete a locally stored provider credential by id. + */ +export const zProviderSecretDeleteRequest_unstable = z.object({ + id: z.string() +}); + +/** + * Look up canonical (bundled-registry) model info for a provider/model pair. + */ +export const zCanonicalModelInfoRequest_unstable = z.object({ + provider: z.string(), + model: z.string() +}); + +export const zCanonicalModelInfoDto = z.object({ + provider: z.string(), + model: z.string(), + contextLimit: z.number().int().gte(0), + maxOutputTokens: z.union([ + z.number().int().gte(0), + z.null() + ]).optional(), + reasoning: z.boolean(), + inputTokenCost: z.union([ + z.number(), + z.null() + ]).optional(), + outputTokenCost: z.union([ + z.number(), + z.null() + ]).optional(), + cacheReadTokenCost: z.union([ + z.number(), + z.null() + ]).optional(), + cacheWriteTokenCost: z.union([ + z.number(), + z.null() + ]).optional(), + currency: z.string() +}); + +export const zCanonicalModelInfoResponse_unstable = z.object({ + modelInfo: z.union([ + zCanonicalModelInfoDto, + z.null() + ]).optional() +}); + export const zPreferenceKey = z.enum([ 'autoCompactThreshold', 'gooseThinkingEffort', @@ -939,8 +1249,14 @@ export const zPreferencesRemoveRequest_unstable = z.object({ export const zDefaultsReadRequest_unstable = z.record(z.unknown()); export const zDefaultsReadResponse_unstable = z.object({ - providerId: z.string().nullish(), - modelId: z.string().nullish() + providerId: z.union([ + z.string(), + z.null() + ]).optional(), + modelId: z.union([ + z.string(), + z.null() + ]).optional() }); /** @@ -948,9 +1264,17 @@ export const zDefaultsReadResponse_unstable = z.object({ */ export const zDefaultsSaveRequest_unstable = z.object({ providerId: z.string(), - modelId: z.string().nullish() + modelId: z.union([ + z.string(), + z.null() + ]).optional() }); +/** + * Clear Goose default provider and model configuration. + */ +export const zDefaultsClearRequest_unstable = z.record(z.unknown()); + /** * Sources that onboarding knows how to discover and import. */ @@ -997,7 +1321,10 @@ export const zOnboardingImportApplyResponse_unstable = z.object({ imported: zOnboardingImportCounts, skipped: zOnboardingImportCounts, warnings: z.array(z.string()).optional().default([]), - providerDefaults: zDefaultsReadResponse_unstable.nullish() + providerDefaults: z.union([ + zDefaultsReadResponse_unstable, + z.null() + ]).optional() }); /** @@ -1033,8 +1360,14 @@ export const zImportSessionRequest_unstable = z.object({ */ export const zImportSessionResponse_unstable = z.object({ sessionId: z.string(), - title: z.string().nullish(), - updatedAt: z.string().nullish(), + title: z.union([ + z.string(), + z.null() + ]).optional(), + updatedAt: z.union([ + z.string(), + z.null() + ]).optional(), messageCount: z.number().int().gte(0) }); @@ -1053,55 +1386,118 @@ export const zShareSessionNostrResponse_unstable = z.object({ export const zRecipeExtensionDto = z.union([ z.object({ name: z.string(), - description: z.string().nullish(), - display_name: z.string().nullish(), - timeout: z.number().int().gte(0).nullish(), - bundled: z.boolean().nullish(), + description: z.union([ + z.string(), + z.null() + ]).optional(), + display_name: z.union([ + z.string(), + z.null() + ]).optional(), + timeout: z.union([ + z.number().int().gte(0), + z.null() + ]).optional(), + bundled: z.union([ + z.boolean(), + z.null() + ]).optional(), type: z.literal('builtin') }), z.object({ name: z.string(), - description: z.string().nullish(), - display_name: z.string().nullish(), - bundled: z.boolean().nullish(), + description: z.union([ + z.string(), + z.null() + ]).optional(), + display_name: z.union([ + z.string(), + z.null() + ]).optional(), + bundled: z.union([ + z.boolean(), + z.null() + ]).optional(), type: z.literal('platform') }), z.object({ name: z.string(), - description: z.string().nullish(), + description: z.union([ + z.string(), + z.null() + ]).optional(), cmd: z.string(), args: z.array(z.string()).optional(), envs: z.record(z.string()).optional(), env_keys: z.array(z.string()).optional(), - timeout: z.number().int().gte(0).nullish(), - cwd: z.string().nullish(), - bundled: z.boolean().nullish(), + timeout: z.union([ + z.number().int().gte(0), + z.null() + ]).optional(), + cwd: z.union([ + z.string(), + z.null() + ]).optional(), + bundled: z.union([ + z.boolean(), + z.null() + ]).optional(), type: z.literal('stdio') }), z.object({ name: z.string(), - description: z.string().nullish(), + description: z.union([ + z.string(), + z.null() + ]).optional(), uri: z.string(), envs: z.record(z.string()).optional(), env_keys: z.array(z.string()).optional(), headers: z.record(z.string()).optional(), - timeout: z.number().int().gte(0).nullish(), - socket: z.string().nullish(), - bundled: z.boolean().nullish(), + timeout: z.union([ + z.number().int().gte(0), + z.null() + ]).optional(), + socket: z.union([ + z.string(), + z.null() + ]).optional(), + bundled: z.union([ + z.boolean(), + z.null() + ]).optional(), type: z.literal('streamable_http') }) ]); export const zRecipeSettingsDto = z.object({ - goose_provider: z.string().nullish(), - goose_model: z.string().nullish(), - temperature: z.number().nullish(), - max_turns: z.number().int().gte(0).nullish() + goose_provider: z.union([ + z.string(), + z.null() + ]).optional(), + goose_model: z.union([ + z.string(), + z.null() + ]).optional(), + temperature: z.union([ + z.number(), + z.null() + ]).optional(), + max_turns: z.union([ + z.number().int().gte(0), + z.null() + ]).optional() }); export const zRecipeAuthorDto = z.object({ - contact: z.string().nullish(), - metadata: z.string().nullish() + contact: z.union([ + z.string(), + z.null() + ]).optional(), + metadata: z.union([ + z.string(), + z.null() + ]).optional() }); export const zRecipeParameterInputTypeDto = z.enum([ @@ -1124,8 +1520,14 @@ export const zRecipeParameterDto = z.object({ input_type: zRecipeParameterInputTypeDto, requirement: zRecipeParameterRequirementDto, description: z.string(), - default: z.string().nullish(), - options: z.array(z.string()).nullish() + default: z.union([ + z.string(), + z.null() + ]).optional(), + options: z.union([ + z.array(z.string()), + z.null() + ]).optional() }); export const zRecipeResponseDto = z.object({ @@ -1135,9 +1537,15 @@ export const zRecipeResponseDto = z.object({ export const zSubRecipeDto = z.object({ name: z.string(), path: z.string(), - values: z.record(z.string()).nullish(), + values: z.union([ + z.record(z.string()), + z.null() + ]).optional(), sequential_when_repeated: z.boolean().optional().default(false), - description: z.string().nullish() + description: z.union([ + z.string(), + z.null() + ]).optional() }); export const zRecipeSuccessCheckDto = z.object({ @@ -1148,25 +1556,64 @@ export const zRecipeSuccessCheckDto = z.object({ export const zRecipeRetryConfigDto = z.object({ max_retries: z.number().int().gte(0), checks: z.array(zRecipeSuccessCheckDto).optional().default([]), - on_failure: z.string().nullish(), - timeout_seconds: z.number().int().gte(0).nullish(), - on_failure_timeout_seconds: z.number().int().gte(0).nullish() + on_failure: z.union([ + z.string(), + z.null() + ]).optional(), + timeout_seconds: z.union([ + z.number().int().gte(0), + z.null() + ]).optional(), + on_failure_timeout_seconds: z.union([ + z.number().int().gte(0), + z.null() + ]).optional() }); export const zRecipeDto = z.object({ version: z.string().optional().default('1.0.0'), title: z.string(), description: z.string(), - instructions: z.string().nullish(), - prompt: z.string().nullish(), - extensions: z.array(zRecipeExtensionDto).nullish(), - settings: zRecipeSettingsDto.nullish(), - activities: z.array(z.string()).nullish(), - author: zRecipeAuthorDto.nullish(), - parameters: z.array(zRecipeParameterDto).nullish(), - response: zRecipeResponseDto.nullish(), - sub_recipes: z.array(zSubRecipeDto).nullish(), - retry: zRecipeRetryConfigDto.nullish() + instructions: z.union([ + z.string(), + z.null() + ]).optional(), + prompt: z.union([ + z.string(), + z.null() + ]).optional(), + extensions: z.union([ + z.array(zRecipeExtensionDto), + z.null() + ]).optional(), + settings: z.union([ + zRecipeSettingsDto, + z.null() + ]).optional(), + activities: z.union([ + z.array(z.string()), + z.null() + ]).optional(), + author: z.union([ + zRecipeAuthorDto, + z.null() + ]).optional(), + parameters: z.union([ + z.array(zRecipeParameterDto), + z.null() + ]).optional(), + response: z.union([ + zRecipeResponseDto, + z.null() + ]).optional(), + sub_recipes: z.union([ + z.array(zSubRecipeDto), + z.null() + ]).optional(), + retry: z.union([ + zRecipeRetryConfigDto, + z.null() + ]).optional() }); export const zEncodeRecipeRequest_unstable = z.object({ @@ -1200,8 +1647,14 @@ export const zRecipeListEntryDto = z.object({ recipe: zRecipeDto, file_path: z.string(), last_modified: z.string(), - schedule_cron: z.string().nullish(), - slash_command: z.string().nullish() + schedule_cron: z.union([ + z.string(), + z.null() + ]).optional(), + slash_command: z.union([ + z.string(), + z.null() + ]).optional() }); export const zListRecipesResponse_unstable = z.object({ @@ -1214,17 +1667,26 @@ export const zDeleteRecipeRequest_unstable = z.object({ export const zScheduleRecipeRequest_unstable = z.object({ id: z.string(), - cron_schedule: z.string().nullish() + cron_schedule: z.union([ + z.string(), + z.null() + ]).optional() }); export const zSetRecipeSlashCommandRequest_unstable = z.object({ id: z.string(), - slash_command: z.string().nullish() + slash_command: z.union([ + z.string(), + z.null() + ]).optional() }); export const zSaveRecipeRequest_unstable = z.object({ recipe: zRecipeDto, - id: z.string().nullish() + id: z.union([ + z.string(), + z.null() + ]).optional() }); export const zSaveRecipeResponse_unstable = z.object({ @@ -1255,11 +1717,20 @@ export const zScheduledJobDto = z.object({ id: z.string(), source: z.string(), cron: z.string(), - lastRun: z.string().nullish(), + lastRun: z.union([ + z.string(), + z.null() + ]).optional(), currentlyRunning: z.boolean(), paused: z.boolean(), - currentSessionId: z.string().nullish(), - jobStartTime: z.string().nullish() + currentSessionId: z.union([ + z.string(), + z.null() + ]).optional(), + jobStartTime: z.union([ + z.string(), + z.null() + ]).optional() }); export const zListSchedulesResponse_unstable = z.object({ @@ -1288,9 +1759,18 @@ export const zSessionInfo = z.object({ sessionId: zSessionId, cwd: z.string(), additionalDirectories: z.array(z.string()).optional(), - title: z.string().nullish(), - updatedAt: z.string().nullish(), - _meta: z.record(z.unknown()).nullish() + title: z.union([ + z.string(), + z.null() + ]).optional(), + updatedAt: z.union([ + z.string(), + z.null() + ]).optional(), + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); export const zListScheduleSessionsResponse_unstable = z.object({ @@ -1336,7 +1816,10 @@ export const zRunScheduleNowStatus = z.enum(['completed', 'cancelled']); export const zRunScheduleNowResponse_unstable = z.object({ status: zRunScheduleNowStatus, - sessionId: z.string().nullish() + sessionId: z.union([ + z.string(), + z.null() + ]).optional() }); export const zKillRunningJobRequest_unstable = z.object({ @@ -1353,9 +1836,18 @@ export const zInspectRunningJobRequest_unstable = z.object({ export const zInspectRunningJobResponse_unstable = z.object({ running: z.boolean(), - sessionId: z.string().nullish(), - jobStartTime: z.string().nullish(), - runningDurationSeconds: z.number().int().nullish() + sessionId: z.union([ + z.string(), + z.null() + ]).optional(), + jobStartTime: z.union([ + z.string(), + z.null() + ]).optional(), + runningDurationSeconds: z.union([ + z.number().int(), + z.null() + ]).optional() }); /** @@ -1382,7 +1874,10 @@ export const zTruncateSessionConversationRequest_unstable = z.object({ */ export const zUpdateSessionProjectRequest_unstable = z.object({ sessionId: z.string(), - projectId: z.string().nullish() + projectId: z.union([ + z.string(), + z.null() + ]).optional() }); /** @@ -1478,8 +1973,14 @@ export const zCreateSourceResponse_unstable = z.object({ * skills. */ export const zListSourcesRequest_unstable = z.object({ - type: zSourceType.nullish(), - projectDir: z.string().nullish(), + type: z.union([ + zSourceType, + z.null() + ]).optional(), + projectDir: z.union([ + z.string(), + z.null() + ]).optional(), includeProjectSources: z.boolean().optional().default(false) }); @@ -1491,8 +1992,14 @@ export const zListSourcesResponse_unstable = z.object({ * List user-facing agent mention targets for `@` autocomplete. */ export const zListAgentMentionsRequest_unstable = z.object({ - cwd: z.string().nullish(), - sessionId: z.string().nullish() + cwd: z.union([ + z.string(), + z.null() + ]).optional(), + sessionId: z.union([ + z.string(), + z.null() + ]).optional() }); /** @@ -1502,7 +2009,10 @@ export const zAgentMention = z.object({ name: z.string(), description: z.string(), sourceType: zSourceType, - sourcePath: z.string().nullish(), + sourcePath: z.union([ + z.string(), + z.null() + ]).optional(), mention: z.string() }); @@ -1514,8 +2024,14 @@ export const zListAgentMentionsResponse_unstable = z.object({ * List slash commands available for `/` autocomplete. */ export const zListSlashCommandsRequest_unstable = z.object({ - cwd: z.string().nullish(), - sessionId: z.string().nullish() + cwd: z.union([ + z.string(), + z.null() + ]).optional(), + sessionId: z.union([ + z.string(), + z.null() + ]).optional() }); /** @@ -1523,7 +2039,10 @@ export const zListSlashCommandsRequest_unstable = z.object({ */ export const zUnstructuredCommandInput = z.object({ hint: z.string(), - _meta: z.record(z.unknown()).nullish() + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); /** @@ -1537,8 +2056,14 @@ export const zAvailableCommandInput = zUnstructuredCommandInput; export const zAvailableCommand = z.object({ name: z.string(), description: z.string(), - input: zAvailableCommandInput.nullish(), - _meta: z.record(z.unknown()).nullish() + input: z.union([ + zAvailableCommandInput, + z.null() + ]).optional(), + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); export const zListSlashCommandsResponse_unstable = z.object({ @@ -1554,7 +2079,10 @@ export const zUpdateSourceRequest_unstable = z.object({ name: z.string(), description: z.string(), content: z.string(), - properties: z.record(z.unknown()).nullish() + properties: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() }); export const zUpdateSourceResponse_unstable = z.object({ @@ -1628,14 +2156,32 @@ export const zDictationModelOption = z.object({ */ export const zDictationProviderStatusEntry = z.object({ configured: z.boolean(), - host: z.string().nullish(), + host: z.union([ + z.string(), + z.null() + ]).optional(), description: z.string(), usesProviderConfig: z.boolean(), - settingsPath: z.string().nullish(), - configKey: z.string().nullish(), - modelConfigKey: z.string().nullish(), - defaultModel: z.string().nullish(), - selectedModel: z.string().nullish(), + settingsPath: z.union([ + z.string(), + z.null() + ]).optional(), + configKey: z.union([ + z.string(), + z.null() + ]).optional(), + modelConfigKey: z.union([ + z.string(), + z.null() + ]).optional(), + defaultModel: z.union([ + z.string(), + z.null() + ]).optional(), + selectedModel: z.union([ + z.string(), + z.null() + ]).optional(), availableModels: z.array(zDictationModelOption).optional().default([]) }); @@ -1698,11 +2244,17 @@ export const zDictationDownloadProgress = z.object({ totalBytes: z.number().int().gte(0), progressPercent: z.number(), status: z.string(), - error: z.string().nullish() + error: z.union([ + z.string(), + z.null() + ]).optional() }); export const zDictationModelDownloadProgressResponse_unstable = z.object({ - progress: zDictationDownloadProgress.nullish() + progress: z.union([ + zDictationDownloadProgress, + z.null() + ]).optional() }); /** @@ -1735,7 +2287,10 @@ export const zSessionUsageUpdate = z.object({ contextLimit: z.number().int().gte(0), accumulatedInputTokens: z.number().int().gte(0), accumulatedOutputTokens: z.number().int().gte(0), - accumulatedCost: z.number().nullish() + accumulatedCost: z.union([ + z.number(), + z.null() + ]).optional() }); export const zStatusMessage = z.union([ @@ -1839,11 +2394,15 @@ export const zExtRequest = z.object({ zProviderConfigSaveRequest_unstable, zProviderConfigDeleteRequest_unstable, zProviderConfigAuthenticateRequest_unstable, + zProviderSecretsListRequest_unstable, + zProviderSecretDeleteRequest_unstable, + zCanonicalModelInfoRequest_unstable, zPreferencesReadRequest_unstable, zPreferencesSaveRequest_unstable, zPreferencesRemoveRequest_unstable, zDefaultsReadRequest_unstable, zDefaultsSaveRequest_unstable, + zDefaultsClearRequest_unstable, zOnboardingImportScanRequest_unstable, zOnboardingImportApplyRequest_unstable, zExportSessionRequest_unstable, @@ -1894,8 +2453,11 @@ export const zExtRequest = z.object({ zDictationModelDeleteRequest_unstable, zDictationModelSelectRequest_unstable ]), - z.record(z.unknown()) - ]).nullish() + z.union([ + z.record(z.unknown()), + z.null() + ]) + ]).optional() }); export const zExtResponse = z.union([ @@ -1932,6 +2494,8 @@ export const zExtResponse = z.union([ zProviderConfigReadResponse_unstable, zProviderConfigStatusResponse_unstable, zProviderConfigChangeResponse_unstable, + zProviderSecretsListResponse_unstable, + zCanonicalModelInfoResponse_unstable, zPreferencesReadResponse_unstable, zDefaultsReadResponse_unstable, zOnboardingImportScanResponse_unstable, @@ -1983,8 +2547,11 @@ export const zExtNotification = z.object({ method: z.string(), params: z.union([ zGooseSessionNotification_unstable, - z.record(z.unknown()) - ]).nullish() + z.union([ + z.record(z.unknown()), + z.null() + ]) + ]).optional() }); export const zExtAgentRequest = z.object({ @@ -1992,8 +2559,11 @@ export const zExtAgentRequest = z.object({ method: z.string(), params: z.union([ zRequestRecipeParams_unstable, - z.record(z.unknown()) - ]).nullish() + z.union([ + z.record(z.unknown()), + z.null() + ]) + ]).optional() }); export const zExtAgentResponse = z.union([