Migrate credentials without touching settings

Richard Feldman created

Change summary

crates/extension_host/src/anthropic_migration.rs    | 20 ++++++++++++--
crates/extension_host/src/copilot_migration.rs      | 12 ++++++--
crates/extension_host/src/google_ai_migration.rs    | 20 ++++++++++++--
crates/extension_host/src/open_router_migration.rs  | 20 ++++++++++++--
crates/extension_host/src/openai_migration.rs       | 20 ++++++++++++--
crates/extension_host/src/wasm_host/llm_provider.rs | 10 ++++++-
6 files changed, 85 insertions(+), 17 deletions(-)

Detailed changes

crates/extension_host/src/anthropic_migration.rs 🔗

@@ -1,5 +1,6 @@
 use credentials_provider::CredentialsProvider;
 use gpui::App;
+use util::ResultExt as _;
 
 const ANTHROPIC_EXTENSION_ID: &str = "anthropic";
 const ANTHROPIC_PROVIDER_ID: &str = "anthropic";
@@ -37,18 +38,31 @@ pub fn migrate_anthropic_credentials_if_needed(extension_id: &str, cx: &mut App)
 
         let api_key = match old_credential {
             Some((_, key_bytes)) => match String::from_utf8(key_bytes) {
-                Ok(key) => key,
+                Ok(key) if !key.is_empty() => key,
+                Ok(_) => {
+                    log::debug!("Existing Anthropic API key is empty, marking as migrated");
+                    String::new()
+                }
                 Err(_) => {
                     log::error!("Failed to decode Anthropic API key as UTF-8");
                     return;
                 }
             },
             None => {
-                log::debug!("No existing Anthropic API key found to migrate");
-                return;
+                log::debug!("No existing Anthropic API key found, marking as migrated");
+                String::new()
             }
         };
 
+        if api_key.is_empty() {
+            // Write empty credentials as a marker that migration was attempted
+            credentials_provider
+                .write_credentials(&extension_credential_key, "Bearer", b"", &cx)
+                .await
+                .log_err();
+            return;
+        }
+
         log::info!("Migrating existing Anthropic API key to Anthropic extension");
 
         match credentials_provider

crates/extension_host/src/copilot_migration.rs 🔗

@@ -1,6 +1,7 @@
 use credentials_provider::CredentialsProvider;
 use gpui::App;
 use std::path::PathBuf;
+use util::ResultExt as _;
 
 const COPILOT_CHAT_EXTENSION_ID: &str = "copilot-chat";
 const COPILOT_CHAT_PROVIDER_ID: &str = "copilot-chat";
@@ -30,9 +31,14 @@ pub fn migrate_copilot_credentials_if_needed(extension_id: &str, cx: &mut App) {
         }
 
         let oauth_token = match read_copilot_oauth_token().await {
-            Some(token) => token,
-            None => {
-                log::debug!("No existing Copilot OAuth token found to migrate");
+            Some(token) if !token.is_empty() => token,
+            _ => {
+                log::debug!("No existing Copilot OAuth token found, marking as migrated");
+                // Write empty credentials as a marker that migration was attempted
+                credentials_provider
+                    .write_credentials(&credential_key, "api_key", b"", &cx)
+                    .await
+                    .log_err();
                 return;
             }
         };

crates/extension_host/src/google_ai_migration.rs 🔗

@@ -1,5 +1,6 @@
 use credentials_provider::CredentialsProvider;
 use gpui::App;
+use util::ResultExt as _;
 
 const GOOGLE_AI_EXTENSION_ID: &str = "google-ai";
 const GOOGLE_AI_PROVIDER_ID: &str = "google-ai";
@@ -37,18 +38,31 @@ pub fn migrate_google_ai_credentials_if_needed(extension_id: &str, cx: &mut App)
 
         let api_key = match old_credential {
             Some((_, key_bytes)) => match String::from_utf8(key_bytes) {
-                Ok(key) => key,
+                Ok(key) if !key.is_empty() => key,
+                Ok(_) => {
+                    log::debug!("Existing Google AI API key is empty, marking as migrated");
+                    String::new()
+                }
                 Err(_) => {
                     log::error!("Failed to decode Google AI API key as UTF-8");
                     return;
                 }
             },
             None => {
-                log::debug!("No existing Google AI API key found to migrate");
-                return;
+                log::debug!("No existing Google AI API key found, marking as migrated");
+                String::new()
             }
         };
 
+        if api_key.is_empty() {
+            // Write empty credentials as a marker that migration was attempted
+            credentials_provider
+                .write_credentials(&extension_credential_key, "Bearer", b"", &cx)
+                .await
+                .log_err();
+            return;
+        }
+
         log::info!("Migrating existing Google AI API key to Google AI extension");
 
         match credentials_provider

crates/extension_host/src/open_router_migration.rs 🔗

@@ -1,5 +1,6 @@
 use credentials_provider::CredentialsProvider;
 use gpui::App;
+use util::ResultExt as _;
 
 const OPEN_ROUTER_EXTENSION_ID: &str = "openrouter";
 const OPEN_ROUTER_PROVIDER_ID: &str = "openrouter";
@@ -37,18 +38,31 @@ pub fn migrate_open_router_credentials_if_needed(extension_id: &str, cx: &mut Ap
 
         let api_key = match old_credential {
             Some((_, key_bytes)) => match String::from_utf8(key_bytes) {
-                Ok(key) => key,
+                Ok(key) if !key.is_empty() => key,
+                Ok(_) => {
+                    log::debug!("Existing OpenRouter API key is empty, marking as migrated");
+                    String::new()
+                }
                 Err(_) => {
                     log::error!("Failed to decode OpenRouter API key as UTF-8");
                     return;
                 }
             },
             None => {
-                log::debug!("No existing OpenRouter API key found to migrate");
-                return;
+                log::debug!("No existing OpenRouter API key found, marking as migrated");
+                String::new()
             }
         };
 
+        if api_key.is_empty() {
+            // Write empty credentials as a marker that migration was attempted
+            credentials_provider
+                .write_credentials(&extension_credential_key, "Bearer", b"", &cx)
+                .await
+                .log_err();
+            return;
+        }
+
         log::info!("Migrating existing OpenRouter API key to OpenRouter extension");
 
         match credentials_provider

crates/extension_host/src/openai_migration.rs 🔗

@@ -1,5 +1,6 @@
 use credentials_provider::CredentialsProvider;
 use gpui::App;
+use util::ResultExt as _;
 
 const OPENAI_EXTENSION_ID: &str = "openai";
 const OPENAI_PROVIDER_ID: &str = "openai";
@@ -37,18 +38,31 @@ pub fn migrate_openai_credentials_if_needed(extension_id: &str, cx: &mut App) {
 
         let api_key = match old_credential {
             Some((_, key_bytes)) => match String::from_utf8(key_bytes) {
-                Ok(key) => key,
+                Ok(key) if !key.is_empty() => key,
+                Ok(_) => {
+                    log::debug!("Existing OpenAI API key is empty, marking as migrated");
+                    String::new()
+                }
                 Err(_) => {
                     log::error!("Failed to decode OpenAI API key as UTF-8");
                     return;
                 }
             },
             None => {
-                log::debug!("No existing OpenAI API key found to migrate");
-                return;
+                log::debug!("No existing OpenAI API key found, marking as migrated");
+                String::new()
             }
         };
 
+        if api_key.is_empty() {
+            // Write empty credentials as a marker that migration was attempted
+            credentials_provider
+                .write_credentials(&extension_credential_key, "Bearer", b"", &cx)
+                .await
+                .log_err();
+            return;
+        }
+
         log::info!("Migrating existing OpenAI API key to OpenAI extension");
 
         match credentials_provider

crates/extension_host/src/wasm_host/llm_provider.rs 🔗

@@ -428,7 +428,10 @@ impl ExtensionProviderConfigurationView {
                 .log_err()
                 .flatten();
 
-            let has_credentials = credentials.is_some();
+            // Treat empty credentials as not authenticated (used as migration marker)
+            let has_credentials = credentials
+                .map(|(_, password)| !password.is_empty())
+                .unwrap_or(false);
 
             // Update authentication state based on stored credentials
             cx.update(|cx| {
@@ -536,7 +539,10 @@ impl ExtensionProviderConfigurationView {
                 .log_err()
                 .flatten();
 
-            let has_credentials = credentials.is_some();
+            // Treat empty credentials as not authenticated (used as migration marker)
+            let has_credentials = credentials
+                .map(|(_, password)| !password.is_empty())
+                .unwrap_or(false);
 
             cx.update(|cx| {
                 state.update(cx, |state, cx| {