1use credentials_provider::CredentialsProvider;
2use gpui::App;
3
4const OPEN_ROUTER_EXTENSION_ID: &str = "openrouter";
5const OPEN_ROUTER_PROVIDER_ID: &str = "openrouter";
6const OPEN_ROUTER_DEFAULT_API_URL: &str = "https://openrouter.ai/api/v1";
7
8/// Migrates OpenRouter API credentials from the old built-in provider location
9/// to the new extension-based location.
10///
11/// This should only be called during auto-install of the extension.
12pub fn migrate_open_router_credentials_if_needed(extension_id: &str, cx: &mut App) {
13 if extension_id != OPEN_ROUTER_EXTENSION_ID {
14 return;
15 }
16
17 let extension_credential_key = format!(
18 "extension-llm-{}:{}",
19 OPEN_ROUTER_EXTENSION_ID, OPEN_ROUTER_PROVIDER_ID
20 );
21
22 let credentials_provider = <dyn CredentialsProvider>::global(cx);
23
24 cx.spawn(async move |cx| {
25 // Read from old location
26 let old_credential = credentials_provider
27 .read_credentials(OPEN_ROUTER_DEFAULT_API_URL, &cx)
28 .await
29 .ok()
30 .flatten();
31
32 let api_key = match old_credential {
33 Some((_, key_bytes)) => match String::from_utf8(key_bytes) {
34 Ok(key) if !key.is_empty() => key,
35 Ok(_) => {
36 log::debug!("Existing OpenRouter API key is empty, nothing to migrate");
37 return;
38 }
39 Err(_) => {
40 log::error!("Failed to decode OpenRouter API key as UTF-8");
41 return;
42 }
43 },
44 None => {
45 log::debug!("No existing OpenRouter API key found to migrate");
46 return;
47 }
48 };
49
50 log::info!("Migrating existing OpenRouter API key to OpenRouter extension");
51
52 match credentials_provider
53 .write_credentials(&extension_credential_key, "Bearer", api_key.as_bytes(), &cx)
54 .await
55 {
56 Ok(()) => {
57 log::info!("Successfully migrated OpenRouter API key to extension");
58 }
59 Err(err) => {
60 log::error!("Failed to migrate OpenRouter API key: {}", err);
61 }
62 }
63 })
64 .detach();
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use gpui::TestAppContext;
71
72 #[gpui::test]
73 async fn test_migrates_credentials_from_old_location(cx: &mut TestAppContext) {
74 let api_key = "sk-or-test-key-12345";
75
76 cx.write_credentials(OPEN_ROUTER_DEFAULT_API_URL, "Bearer", api_key.as_bytes());
77
78 cx.update(|cx| {
79 migrate_open_router_credentials_if_needed(OPEN_ROUTER_EXTENSION_ID, cx);
80 });
81
82 cx.run_until_parked();
83
84 let migrated = cx.read_credentials("extension-llm-openrouter:openrouter");
85 assert!(migrated.is_some(), "Credentials should have been migrated");
86 let (username, password) = migrated.unwrap();
87 assert_eq!(username, "Bearer");
88 assert_eq!(String::from_utf8(password).unwrap(), api_key);
89 }
90
91 #[gpui::test]
92 async fn test_no_migration_if_no_old_credentials(cx: &mut TestAppContext) {
93 cx.update(|cx| {
94 migrate_open_router_credentials_if_needed(OPEN_ROUTER_EXTENSION_ID, cx);
95 });
96
97 cx.run_until_parked();
98
99 let credentials = cx.read_credentials("extension-llm-openrouter:openrouter");
100 assert!(
101 credentials.is_none(),
102 "Should not create credentials if none existed"
103 );
104 }
105
106 #[gpui::test]
107 async fn test_skips_migration_for_other_extensions(cx: &mut TestAppContext) {
108 let api_key = "sk-or-test-key";
109
110 cx.write_credentials(OPEN_ROUTER_DEFAULT_API_URL, "Bearer", api_key.as_bytes());
111
112 cx.update(|cx| {
113 migrate_open_router_credentials_if_needed("some-other-extension", cx);
114 });
115
116 cx.run_until_parked();
117
118 let credentials = cx.read_credentials("extension-llm-openrouter:openrouter");
119 assert!(
120 credentials.is_none(),
121 "Should not migrate for other extensions"
122 );
123 }
124}