From cf1ae373695514b858a5704617ecc4e4f4de8edd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 20 Aug 2026 15:35:11 -0500 Subject: [PATCH] Remove default AI providers and models --- app/src/ai/llms.rs | 39 --------- app/src/ai/llms_tests.rs | 95 ---------------------- app/src/settings/ai.rs | 136 +++----------------------------- app/src/settings/ai_tests.rs | 86 -------------------- plans/galaxy-local-first-rig.md | 4 +- 5 files changed, 12 insertions(+), 348 deletions(-) diff --git a/app/src/ai/llms.rs b/app/src/ai/llms.rs index 6b61a05f..545a920e 100644 --- a/app/src/ai/llms.rs +++ b/app/src/ai/llms.rs @@ -735,7 +735,6 @@ impl LLMPreferences { #[cfg(not(target_family = "wasm"))] { - Self::ensure_default_chatgpt_models_in_settings(ctx); me.refresh_bedrock_models(ctx); me.inject_openai_models(ctx); me.ensure_default_model_present(); @@ -747,44 +746,6 @@ impl LLMPreferences { me } - #[cfg(not(target_family = "wasm"))] - fn ensure_default_chatgpt_models_in_settings(ctx: &mut ModelContext) { - let mut providers = AISettings::as_ref(ctx).openai_providers.value().clone(); - let default_chatgpt_models = crate::settings::ai::default_chatgpt_provider().models; - let mut providers_changed = false; - for provider in &mut providers { - if provider.kind != OpenAIProviderKind::ChatGPTSubscription { - continue; - } - - if provider.models.is_empty() { - provider.models = default_chatgpt_models.clone(); - providers_changed = true; - continue; - } - - for model in &mut provider.models { - if !model.reasoning_efforts.is_empty() { - continue; - } - if let Some(default_model) = default_chatgpt_models - .iter() - .find(|default_model| default_model.model_id == model.model_id) - { - if !default_model.reasoning_efforts.is_empty() { - model.reasoning_efforts = default_model.reasoning_efforts.clone(); - providers_changed = true; - } - } - } - } - if providers_changed { - AISettings::handle(ctx).update(ctx, |settings, ctx| { - let _ = settings.openai_providers.set_value(providers, ctx); - }); - } - } - #[cfg(not(target_family = "wasm"))] fn refresh_bedrock_models(&mut self, ctx: &mut ModelContext) { let settings = AISettings::as_ref(ctx); diff --git a/app/src/ai/llms_tests.rs b/app/src/ai/llms_tests.rs index 5189fe33..0dffec56 100644 --- a/app/src/ai/llms_tests.rs +++ b/app/src/ai/llms_tests.rs @@ -724,101 +724,6 @@ fn disabled_providers_do_not_leave_models_in_the_runtime_inventory() { }); } -#[test] -fn chatgpt_reasoning_modes_route_with_catalog_context_metadata() { - App::test((), |mut app| async move { - initialize_settings_for_tests(&mut app); - let provider = crate::settings::ai::default_chatgpt_provider(); - let configured_model = |model_id: &str| { - provider - .models - .iter() - .find(|model| model.model_id == model_id) - .expect("ChatGPT model should be configured") - }; - - let gpt_54 = configured_model("gpt-5.4"); - assert_eq!(gpt_54.context_size, 1_000_000); - assert_eq!(gpt_54.max_input_tokens, Some(950_000)); - let gpt_56_sol = configured_model("gpt-5.6-sol"); - assert_eq!(gpt_56_sol.context_size, 272_000); - assert_eq!(gpt_56_sol.max_input_tokens, Some(258_400)); - let uncached_model = configured_model("gpt-5.4-pro"); - assert_eq!(uncached_model.context_size, 200_000); - assert_eq!(uncached_model.max_input_tokens, None); - - AISettings::handle(&app).update(&mut app, |settings, ctx| { - settings - .bedrock_enabled - .set_value(false, ctx) - .expect("Bedrock setting should update"); - settings - .acp_enabled - .set_value(false, ctx) - .expect("ACP setting should update"); - settings - .openai_enabled - .set_value(true, ctx) - .expect("OpenAI setting should update"); - settings - .openai_models - .set_value(Vec::new(), ctx) - .expect("OpenAI model setting should update"); - settings - .openai_providers - .set_value(vec![provider], ctx) - .expect("OpenAI provider setting should update"); - }); - - let mut preferences = empty_preferences(); - app.read(|ctx| preferences.inject_openai_models(ctx)); - - let model_info = |model_id: &str| { - preferences - .models_by_feature - .agent_mode - .choices - .iter() - .find(|model| model.id.as_str() == model_id) - .expect("ChatGPT model should be available") - }; - let assert_fixed_context = |model_id: &str, expected: u32| { - let context_window = &model_info(model_id).context_window; - assert!(!context_window.is_configurable); - assert_eq!(context_window.min, expected); - assert_eq!(context_window.max, expected); - assert_eq!(context_window.default_max, expected); - }; - - let mode_id = "gpt-5.4::reasoning::high"; - let mode = model_info(mode_id); - assert_eq!(mode.reasoning_level.as_deref(), Some("high")); - assert_fixed_context(mode_id, 950_000); - let routing = preferences - .openai_client_config_for_model(mode_id) - .expect("reasoning mode should have a routing entry"); - assert_eq!(routing.model.as_deref(), Some("gpt-5.4")); - assert_eq!(routing.reasoning_effort.as_deref(), Some("high")); - assert_eq!(routing.max_input_tokens, Some(950_000)); - - let ultra_id = "gpt-5.6-sol::reasoning::ultra"; - assert_fixed_context(ultra_id, 258_400); - let ultra_routing = preferences - .openai_client_config_for_model(ultra_id) - .expect("GPT-5.6 Sol ultra mode should have a routing entry"); - assert_eq!(ultra_routing.model.as_deref(), Some("gpt-5.6-sol")); - assert_eq!(ultra_routing.reasoning_effort.as_deref(), Some("ultra")); - assert_eq!(ultra_routing.max_input_tokens, Some(258_400)); - - let uncached_id = "gpt-5.4-pro"; - assert_fixed_context(uncached_id, 200_000); - let uncached_routing = preferences - .openai_client_config_for_model(uncached_id) - .expect("GPT-5.4 Pro should have a routing entry"); - assert_eq!(uncached_routing.max_input_tokens, Some(200_000)); - }); -} - #[test] fn provider_discovery_enables_rig_for_new_models_and_keeps_manual_models() { let manual = openai_model("manual-model"); diff --git a/app/src/settings/ai.rs b/app/src/settings/ai.rs index c972afa0..5c457276 100644 --- a/app/src/settings/ai.rs +++ b/app/src/settings/ai.rs @@ -1029,9 +1029,6 @@ pub struct OpenAIProviderConfig { impl settings_value::SettingsValue for OpenAIProviderConfig {} -const INITIAL_LITELLM_BASE_URL: &str = "https://ai.ryserve.net/v1"; -const INITIAL_RIG_MODEL_ID: &str = "codex-gpt-5.6-sol-xhigh"; - fn default_acp_agent_id() -> String { "codex".to_string() } @@ -1116,119 +1113,6 @@ impl AcpProviderConfig { impl settings_value::SettingsValue for AcpProviderConfig {} -fn default_chatgpt_models() -> Vec { - // Fallback catalog used before the first successful Codex model discovery. - // Once discovery succeeds, the saved ChatGPT subscription catalog is treated - // as backend-owned so removed models do not get reintroduced on startup. - [ - ( - "gpt-5.6-sol", - "GPT-5.6 Sol", - vec!["low", "medium", "high", "xhigh", "max", "ultra"], - 272_000, - Some(258_400), - ), - ( - "gpt-5.6-terra", - "GPT-5.6 Terra", - vec!["low", "medium", "high", "xhigh", "max", "ultra"], - 272_000, - Some(258_400), - ), - ( - "gpt-5.6-luna", - "GPT-5.6 Luna", - vec!["low", "medium", "high", "xhigh", "max", "ultra"], - 272_000, - Some(258_400), - ), - ( - "gpt-5.4", - "GPT-5.4", - vec!["low", "medium", "high", "xhigh"], - 1_000_000, - Some(950_000), - ), - ( - "gpt-5.4-pro", - "GPT-5.4 Pro", - vec!["medium", "high", "xhigh"], - default_context_size(), - None, - ), - ] - .into_iter() - .map( - |(model_id, display_name, reasoning_efforts, context_size, max_input_tokens)| { - OpenAIModelConfig { - model_id: model_id.to_string(), - display_name: display_name.to_string(), - // ChatGPT's subscription backend accepts image input for its chat - // models, but it does not expose a public capability discovery - // endpoint. Keep this explicit catalog in sync with that contract - // so the model picker does not hide vision context. - vision_supported: true, - context_size, - max_input_tokens, - max_output_tokens: None, - provider: Some("openai".to_string()), - use_rig: true, - supports_system_messages: Some(true), - capability_overrides: HashMap::new(), - reasoning_efforts: reasoning_efforts.into_iter().map(str::to_string).collect(), - enabled: true, - } - }, - ) - .collect() -} - -pub(crate) fn default_chatgpt_provider() -> OpenAIProviderConfig { - OpenAIProviderConfig { - kind: OpenAIProviderKind::ChatGPTSubscription, - enabled: true, - name: "ChatGPT Subscription".to_string(), - base_url: String::new(), - api_key: None, - project_id: None, - location: None, - models: default_chatgpt_models(), - } -} - -fn default_openai_providers() -> Vec { - vec![ - OpenAIProviderConfig { - kind: OpenAIProviderKind::LiteLLM, - enabled: true, - name: "LiteLLM (ai.ryserve.net)".to_string(), - base_url: INITIAL_LITELLM_BASE_URL.to_string(), - // Credentials are deliberately never committed. Set this locally in - // ~/.galaxy/settings.toml before sending a request. - api_key: None, - project_id: None, - location: None, - models: vec![OpenAIModelConfig { - model_id: INITIAL_RIG_MODEL_ID.to_string(), - display_name: "Codex GPT-5.6 SOL (xhigh)".to_string(), - // Auto capability detection is optimistic for modern - // multimodal-compatible endpoints; users can override it per model. - vision_supported: true, - context_size: default_context_size(), - max_input_tokens: None, - max_output_tokens: None, - provider: Some("openai".to_string()), - use_rig: true, - supports_system_messages: Some(false), - capability_overrides: HashMap::new(), - reasoning_efforts: Vec::new(), - enabled: true, - }], - }, - default_chatgpt_provider(), - ] -} - /// Cached metadata and runtime session options for an ACP agent. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)] pub struct AcpAgentSettings { @@ -1286,7 +1170,7 @@ define_settings_group!(AISettings, settings: [ // If `false`, all AI features are disabled. is_any_ai_enabled: IsAnyAIEnabled { type: bool, - default: true, + default: false, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, private: false, @@ -1804,20 +1688,20 @@ define_settings_group!(AISettings, settings: [ toml_path: "cloud_platform.third_party_api_keys.gemini_enterprise_credentials_enabled", description: "Whether Warp should route eligible requests through your workspace's Gemini Enterprise Google Cloud project.", } - // Whether the OpenAI-compatible (LiteLLM) provider is enabled. + // Whether a configured OpenAI-compatible provider is enabled. openai_enabled: OpenAIEnabled { type: bool, - default: true, + default: false, supported_platforms: SupportedPlatforms::DESKTOP, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "ai.openai.enabled", - description: "Whether to use an OpenAI-compatible endpoint (e.g. LiteLLM) for AI requests.", + description: "Whether to use a configured OpenAI-compatible endpoint for AI requests.", } // Base URL for the OpenAI-compatible API endpoint. openai_base_url: OpenAIBaseUrl { type: String, - default: "http://localhost:4000/v1".to_string(), + default: String::new(), supported_platforms: SupportedPlatforms::DESKTOP, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, @@ -1844,7 +1728,7 @@ define_settings_group!(AISettings, settings: [ toml_path: "ai.openai.model", description: "Model name to send to the OpenAI-compatible endpoint. Leave empty to use the selected model ID.", } - // Custom OpenAI-compatible model configurations (fetched from LiteLLM or manually configured). + // Custom OpenAI-compatible model configurations (fetched or manually configured). openai_models: OpenAIModels { type: Vec, default: Vec::new(), @@ -1852,20 +1736,20 @@ define_settings_group!(AISettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "ai.openai.models", - description: "Custom OpenAI-compatible model configurations (e.g. from LiteLLM).", + description: "Custom OpenAI-compatible model configurations.", } - // Multiple OpenAI-compatible provider endpoints (LiteLLM, Ollama, vLLM, etc.). + // Multiple OpenAI-compatible provider endpoints. // Each provider has its own name, base_url, api_key, and model list. openai_providers: OpenAIProviders { type: Vec, - default: default_openai_providers(), + default: Vec::new(), supported_platforms: SupportedPlatforms::DESKTOP, // Provider entries may contain API keys, so the complete setting must // remain local even when preference sync is enabled. sync_to_cloud: SyncToCloud::Never, private: false, toml_path: "ai.providers", - description: "Multiple OpenAI-compatible provider endpoints (e.g. LiteLLM, Ollama, local models).", + description: "Multiple OpenAI-compatible provider endpoints.", } // Whether to send opt-in AI diagnostics to a remote logging endpoint. remote_logging_enabled: RemoteLoggingEnabled { diff --git a/app/src/settings/ai_tests.rs b/app/src/settings/ai_tests.rs index cd5fd3ee..b412b059 100644 --- a/app/src/settings/ai_tests.rs +++ b/app/src/settings/ai_tests.rs @@ -345,77 +345,6 @@ fn test_toolbar_command_map_roundtrip() { assert_eq!(original, restored); } -#[test] -fn initial_litellm_provider_maps_codex_model_to_rig_without_a_committed_key() { - let providers = default_openai_providers(); - - assert_eq!(providers.len(), 2); - let provider = &providers[0]; - assert_eq!(provider.kind, OpenAIProviderKind::LiteLLM); - assert_eq!(provider.base_url, INITIAL_LITELLM_BASE_URL); - assert_eq!(provider.api_key, None); - assert_eq!(provider.models.len(), 1); - let model = &provider.models[0]; - assert_eq!(model.model_id, INITIAL_RIG_MODEL_ID); - assert!(model.use_rig); - assert_eq!(model.supports_system_messages, Some(false)); - assert!(!model.supports_system_messages()); - - let chatgpt = &providers[1]; - assert_eq!(chatgpt.kind, OpenAIProviderKind::ChatGPTSubscription); - assert_eq!(chatgpt.name, "ChatGPT Subscription"); - assert!(chatgpt.base_url.is_empty()); - assert!(chatgpt.api_key.is_none()); - assert!(chatgpt - .models - .iter() - .any(|model| model.model_id == "gpt-5.4-pro")); - - let sol = chatgpt - .models - .iter() - .find(|model| model.model_id == "gpt-5.6-sol") - .expect("GPT-5.6 Sol should be in the ChatGPT catalog"); - assert_eq!(sol.reasoning_efforts.len(), 6); - assert!(sol.reasoning_efforts.iter().any(|effort| effort == "max")); - assert!(sol.reasoning_efforts.iter().any(|effort| effort == "ultra")); - - let luna = chatgpt - .models - .iter() - .find(|model| model.model_id == "gpt-5.6-luna") - .expect("GPT-5.6 Luna should be in the ChatGPT catalog"); - assert!(luna.reasoning_efforts.iter().any(|effort| effort == "max")); - assert!(luna - .reasoning_efforts - .iter() - .any(|effort| effort == "ultra")); - - let terra = chatgpt - .models - .iter() - .find(|model| model.model_id == "gpt-5.6-terra") - .expect("GPT-5.6 Terra should be in the ChatGPT catalog"); - assert!(terra.reasoning_efforts.iter().any(|effort| effort == "max")); - assert!(terra - .reasoning_efforts - .iter() - .any(|effort| effort == "ultra")); - - let gpt_54 = chatgpt - .models - .iter() - .find(|model| model.model_id == "gpt-5.4") - .expect("GPT-5.4 should be in the ChatGPT catalog"); - assert_eq!( - gpt_54.reasoning_efforts, - vec!["low", "medium", "high", "xhigh"] - .into_iter() - .map(str::to_string) - .collect::>() - ); -} - #[test] fn native_provider_settings_roundtrip_with_vertex_configuration() { let provider: OpenAIProviderConfig = serde_json::from_value(serde_json::json!({ @@ -464,21 +393,6 @@ fn native_provider_settings_roundtrip_with_vertex_configuration() { assert_eq!(native_openai.kind, OpenAIProviderKind::OpenAI); } -#[test] -fn codex_litellm_model_infers_missing_system_message_capability() { - let mut model = default_openai_providers().remove(0).models.remove(0); - model.supports_system_messages = None; - - assert!(!model.supports_system_messages()); - - model.model_id = "gpt-4o".to_string(); - assert!(model.supports_system_messages()); - - model.model_id = INITIAL_RIG_MODEL_ID.to_string(); - model.supports_system_messages = Some(true); - assert!(model.supports_system_messages()); -} - #[test] fn test_toolbar_command_map_matched_agent() { App::test((), |mut app| async move { diff --git a/plans/galaxy-local-first-rig.md b/plans/galaxy-local-first-rig.md index 7f25a650..9e23c072 100644 --- a/plans/galaxy-local-first-rig.md +++ b/plans/galaxy-local-first-rig.md @@ -278,8 +278,8 @@ Initial opt-in example: enabled = true [[ai.providers]] -name = "LiteLLM (ai.ryserve.net)" -base_url = "https://ai.ryserve.net/v1" +name = "My local provider" +base_url = "https://your-provider.example/v1" api_key = "REPLACE_WITH_LOCAL_KEY" [[ai.providers.models]]