Skip to content

Commit 163758a

Browse files
committed
fix: migrate Custom OpenAI services to use UUIDs and several other fixes
1 parent fdfde42 commit 163758a

27 files changed

+528
-381
lines changed

src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceFormTabbedPane.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,32 @@ public Map<String, Object> getBody() {
5858

5959
private void setTableData(JBTable table, Map<String, ?> values) {
6060
DefaultTableModel model = (DefaultTableModel) table.getModel();
61-
model.setRowCount(0);
6261

63-
for (var entry : values.entrySet()) {
64-
model.addRow(new Object[]{entry.getKey(), entry.getValue()});
62+
if (hasTableDataChanged(model, values)) {
63+
model.setRowCount(0);
64+
for (var entry : values.entrySet()) {
65+
model.addRow(new Object[]{entry.getKey(), entry.getValue()});
66+
}
6567
}
6668
}
6769

70+
private boolean hasTableDataChanged(DefaultTableModel model, Map<String, ?> newValues) {
71+
if (model.getRowCount() != newValues.size()) {
72+
return true;
73+
}
74+
75+
for (int i = 0; i < model.getRowCount(); i++) {
76+
String key = (String) model.getValueAt(i, 0);
77+
Object value = model.getValueAt(i, 1);
78+
79+
if (!newValues.containsKey(key) || !java.util.Objects.equals(newValues.get(key), value)) {
80+
return true;
81+
}
82+
}
83+
84+
return false;
85+
}
86+
6887
private Map<String, Object> getTableData(JBTable table) {
6988
var model = (DefaultTableModel) table.getModel();
7089
var data = new HashMap<String, Object>();

src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,11 @@ private void updateTemplatePresentation(ServiceType selectedService) {
312312
break;
313313
case CUSTOM_OPENAI:
314314
ModelRegistry.getInstance().getCustomOpenAIModels().stream()
315-
.filter(it -> Objects.requireNonNull(modelCode).equals(it.getModel()))
315+
.filter(it -> it.getModel().equals(modelCode))
316316
.findFirst()
317317
.ifPresent(selection -> {
318318
templatePresentation.setIcon(Icons.OpenAI);
319-
templatePresentation.setText(selection.getModel());
319+
templatePresentation.setText(selection.getDisplayName());
320320
});
321321
break;
322322
case ANTHROPIC:
@@ -463,7 +463,7 @@ private AnAction createCustomOpenAIModelAction(
463463
Icons.OpenAI,
464464
comboBoxPresentation,
465465
() -> ApplicationManager.getApplication().getService(ModelSettings.class)
466-
.setModel(featureType, model.getName(), CUSTOM_OPENAI));
466+
.setModel(featureType, model.getId(), CUSTOM_OPENAI));
467467
}
468468

469469
private AnAction createGoogleModelAction(GoogleModel model, Presentation comboBoxPresentation) {

src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ object CodeCompletionRequestFactory {
139139
val activeService = service<CustomServicesSettings>()
140140
.customServiceStateForFeatureType(FeatureType.CODE_COMPLETION)
141141
val settings = activeService.codeCompletionSettings
142-
val credential =
143-
getCredential(CredentialKey.CustomServiceApiKey(activeService.name.orEmpty()))
142+
val credential = getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(activeService.id)))
144143
return buildCustomRequest(
145144
details,
146145
settings.url!!,

src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package ee.carlrobert.codegpt.codecompletions
33
import com.intellij.openapi.components.Service
44
import com.intellij.openapi.components.service
55
import com.intellij.openapi.project.Project
6-
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildChatBasedFIMRequest
76
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildChatBasedFIMHttpRequest
87
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildCustomRequest
98
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildLlamaRequest
@@ -68,13 +67,13 @@ class CodeCompletionService(private val project: Project) {
6867
}
6968

7069
CUSTOM_OPENAI -> {
71-
val activeService = service<CustomServicesSettings>().state.active
70+
val activeService =
71+
service<CustomServicesSettings>().customServiceStateForFeatureType(FeatureType.CODE_COMPLETION)
7272
val customSettings = activeService.codeCompletionSettings
73-
val isChatBasedFIM = customSettings.infillTemplate == InfillPromptTemplate.CHAT_COMPLETION
74-
73+
val isChatBasedFIM =
74+
customSettings.infillTemplate == InfillPromptTemplate.CHAT_COMPLETION
7575
if (isChatBasedFIM) {
76-
// Use chat completion endpoint for chat-based FIM with proper API key substitution
77-
val credential = getCredential(CredentialKey.CustomServiceApiKey(activeService.name.orEmpty()))
76+
val credential = getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(activeService.id)))
7877
createFactory(
7978
CompletionClientProvider.getDefaultClientBuilder().build()
8079
).newEventSource(
@@ -88,7 +87,6 @@ class CodeCompletionService(private val project: Project) {
8887
OpenAIChatCompletionEventSourceListener(eventListener)
8988
)
9089
} else {
91-
// Use traditional completion endpoint
9290
createFactory(
9391
CompletionClientProvider.getDefaultClientBuilder().build()
9492
).newEventSource(

src/main/kotlin/ee/carlrobert/codegpt/completions/factory/CustomOpenAIRequestFactory.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class CustomOpenAIRequestFactory : BaseRequestFactory() {
3333
params.psiStructure
3434
),
3535
true,
36-
getCredential(CredentialKey.CustomServiceApiKey(service.name.orEmpty()))
36+
getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(service.id)))
3737
)
3838
return CustomOpenAIRequest(request)
3939
}
@@ -54,7 +54,7 @@ class CustomOpenAIRequestFactory : BaseRequestFactory() {
5454
OpenAIChatCompletionStandardMessage("user", userPrompt)
5555
),
5656
stream,
57-
getCredential(CredentialKey.CustomServiceApiKey(service.name.orEmpty()))
57+
getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(service.id)))
5858
)
5959
return CustomOpenAIRequest(request)
6060
}
@@ -67,7 +67,7 @@ class CustomOpenAIRequestFactory : BaseRequestFactory() {
6767
service.chatCompletionSettings,
6868
messages,
6969
true,
70-
getCredential(CredentialKey.CustomServiceApiKey(service.name.orEmpty()))
70+
getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(service.id)))
7171
)
7272
return CustomOpenAIRequest(request)
7373
}

src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,15 @@ object CredentialsStore {
4848
override val value: String = "OPENAI_API_KEY"
4949
}
5050

51+
@Deprecated("Only for migration")
5152
data class CustomServiceApiKey(val name: String) : CredentialKey() {
5253
override val value: String = "CUSTOM_SERVICE_API_KEY:$name"
5354
}
5455

56+
data class CustomServiceApiKeyById(val id: String) : CredentialKey() {
57+
override val value: String = "CUSTOM_SERVICE_API_KEY_ID:$id"
58+
}
59+
5560
@Deprecated("Only for migration")
5661
data object CustomServiceApiKeyLegacy : CredentialKey() {
5762
override val value: String = "CUSTOM_SERVICE_API_KEY"
@@ -73,4 +78,4 @@ object CredentialsStore {
7378
override val value: String = "MISTRAL_API_KEY"
7479
}
7580
}
76-
}
81+
}

src/main/kotlin/ee/carlrobert/codegpt/settings/Placeholder.kt

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package ee.carlrobert.codegpt.settings
22

3+
import com.intellij.openapi.diagnostic.thisLogger
34
import com.intellij.openapi.project.Project
4-
import git4idea.GitUtil
5+
import ee.carlrobert.codegpt.util.GitUtil
56
import git4idea.branch.GitBranchUtil
67
import java.time.LocalDate
78

@@ -16,7 +17,10 @@ enum class Placeholder(val description: String, val code: String) {
1617
"The complete source code contents of all files currently open in the IDE editor tabs, maintaining their formatting and structure.",
1718
"$" + "OPEN_FILES"
1819
),
19-
ACTIVE_CONVERSATION("The complete conversation history with the AI assistant, including the most recent response and any relevant context from the current interaction.", "$" + "ACTIVE_CONVERSATION"),
20+
ACTIVE_CONVERSATION(
21+
"The complete conversation history with the AI assistant, including the most recent response and any relevant context from the current interaction.",
22+
"$" + "ACTIVE_CONVERSATION"
23+
),
2024
PREFIX("Code before the cursor.", "$" + "PREFIX"),
2125
SUFFIX("Code after the cursor.", "$" + "SUFFIX"),
2226
FIM_PROMPT(
@@ -36,15 +40,19 @@ class DatePlaceholderStrategy : PlaceholderStrategy {
3640
}
3741

3842
class BranchNamePlaceholderStrategy(val project: Project) : PlaceholderStrategy {
43+
private val logger = thisLogger()
44+
3945
override fun getReplacementValue(): String {
4046
return try {
41-
val repositories = GitUtil.getRepositoryManager(project).repositories
42-
if (repositories.isEmpty() || repositories.size != 1) {
47+
val repository = GitUtil.getProjectRepository(project)
48+
if (repository == null) {
49+
logger.error("Couldn't find repository for project")
4350
return "BRANCH-UNKNOWN"
4451
}
4552

46-
GitBranchUtil.getBranchNameOrRev(repositories[0])
47-
} catch (ignore: Exception) {
53+
GitBranchUtil.getBranchNameOrRev(repository)
54+
} catch (ex: Exception) {
55+
logger.error("Couldn't get git branch name replacement value", ex)
4856
"BRANCH-UNKNOWN"
4957
}
5058
}

src/main/kotlin/ee/carlrobert/codegpt/settings/migration/LegacySettingsMigration.kt

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ object LegacySettingsMigration {
2424
return try {
2525
val generalState = GeneralSettings.getCurrentState()
2626
val selectedService = generalState.selectedService
27-
27+
2828
if (selectedService != null) {
2929
generalState.selectedService = null
3030
createMigratedState(selectedService)
@@ -40,7 +40,7 @@ object LegacySettingsMigration {
4040
private fun createMigratedState(selectedService: ServiceType): ModelSettingsState {
4141
return ModelSettingsState().apply {
4242
val chatModel = getLegacyChatModelForService(selectedService)
43-
43+
4444
setModelSelection(FeatureType.CHAT, chatModel, selectedService)
4545
setModelSelection(FeatureType.AUTO_APPLY, chatModel, selectedService)
4646
setModelSelection(FeatureType.COMMIT_MESSAGE, chatModel, selectedService)
@@ -49,7 +49,7 @@ object LegacySettingsMigration {
4949

5050
val codeModel = getLegacyCodeModelForService(selectedService)
5151
setModelSelection(FeatureType.CODE_COMPLETION, codeModel, selectedService)
52-
52+
5353
if (selectedService == ServiceType.PROXYAI) {
5454
setModelSelection(FeatureType.NEXT_EDIT, ModelRegistry.ZETA, ServiceType.PROXYAI)
5555
} else {
@@ -71,7 +71,8 @@ object LegacySettingsMigration {
7171
}
7272

7373
ServiceType.ANTHROPIC -> {
74-
AnthropicSettings.getCurrentState().model ?: ModelRegistry.CLAUDE_SONNET_4_20250514
74+
AnthropicSettings.getCurrentState().model
75+
?: ModelRegistry.CLAUDE_SONNET_4_20250514
7576
}
7677

7778
ServiceType.GOOGLE -> {
@@ -94,15 +95,10 @@ object LegacySettingsMigration {
9495
}
9596

9697
ServiceType.CUSTOM_OPENAI -> {
97-
val customServicesSettings = service<CustomServicesSettings>()
98-
val services = customServicesSettings.state.services
99-
100-
val activeServiceName = customServicesSettings.state.active.name
101-
if (!activeServiceName.isNullOrBlank()) {
102-
activeServiceName
103-
} else {
104-
services.map { it.name }.lastOrNull()?.takeIf { it.isNotBlank() } ?: "Default"
105-
}
98+
service<CustomServicesSettings>().state.services
99+
.map { it.name }
100+
.lastOrNull()
101+
?.takeIf { it.isNotBlank() } ?: "Default"
106102
}
107103

108104
ServiceType.MISTRAL -> {
@@ -111,7 +107,7 @@ object LegacySettingsMigration {
111107
}
112108
} catch (e: Exception) {
113109
logger.warn("Failed to get legacy chat model for $serviceType", e)
114-
getDefaultModelForService(serviceType)
110+
throw e
115111
}
116112
}
117113

@@ -162,28 +158,4 @@ object LegacySettingsMigration {
162158
null
163159
}
164160
}
165-
166-
private fun getDefaultModelForService(serviceType: ServiceType): String {
167-
return when (serviceType) {
168-
ServiceType.PROXYAI -> ModelRegistry.GEMINI_FLASH_2_5
169-
ServiceType.OPENAI -> ModelRegistry.GPT_4O
170-
ServiceType.ANTHROPIC -> ModelRegistry.CLAUDE_SONNET_4_20250514
171-
ServiceType.GOOGLE -> ModelRegistry.GEMINI_2_0_FLASH
172-
ServiceType.MISTRAL -> ModelRegistry.DEVSTRAL_MEDIUM_2507
173-
ServiceType.OLLAMA -> ModelRegistry.LLAMA_3_2
174-
ServiceType.LLAMA_CPP -> ModelRegistry.LLAMA_3_2_3B_INSTRUCT
175-
ServiceType.CUSTOM_OPENAI -> {
176-
// For Custom OpenAI, try to use the active service name if available
177-
// If not available, use a placeholder that won't break model selection
178-
try {
179-
val customServicesSettings = service<CustomServicesSettings>()
180-
val activeService = customServicesSettings.state.active
181-
activeService?.name?.takeIf { it.isNotBlank() } ?: "Custom OpenAI"
182-
} catch (e: Exception) {
183-
logger.warn("Could not access CustomServicesSettings for default model, using placeholder", e)
184-
"Custom OpenAI"
185-
}
186-
}
187-
}
188-
}
189161
}

src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistry.kt

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -286,17 +286,15 @@ class ModelRegistry {
286286
return try {
287287
val customServicesSettings = service<CustomServicesSettings>()
288288
customServicesSettings.state.services.mapNotNull { service ->
289-
if (service.name.isNullOrBlank()) {
290-
return@mapNotNull null
291-
}
292-
293-
service.name?.let { serviceName ->
294-
val modelFromBody = service.codeCompletionSettings.body["model"]
295-
val modelName = (modelFromBody as? String) ?: "Unknown Model"
296-
val displayName = "$serviceName ($modelName)"
297-
298-
ModelSelection(ServiceType.CUSTOM_OPENAI, serviceName, displayName)
299-
}
289+
val serviceId = service.id ?: return@mapNotNull null
290+
val serviceName = service.name ?: ""
291+
val modelFromBody = service.codeCompletionSettings.body["model"]
292+
val modelName = (modelFromBody as? String)
293+
val displayName = if (!modelName.isNullOrEmpty()) {
294+
if (modelName.length > 20) "$serviceName (...${modelName.takeLast(20)})" else "$serviceName ($modelName)"
295+
} else serviceName
296+
297+
ModelSelection(ServiceType.CUSTOM_OPENAI, serviceId, displayName)
300298
}
301299
} catch (e: Exception) {
302300
logger.error("Failed to get Custom OpenAI code models", e)
@@ -524,20 +522,14 @@ class ModelRegistry {
524522
return try {
525523
val customServicesSettings = service<CustomServicesSettings>()
526524
customServicesSettings.state.services.mapNotNull { service ->
527-
if (service.name.isNullOrBlank()) {
528-
return@mapNotNull null
529-
}
530-
531-
service.name?.let { serviceName ->
532-
val modelName = service.chatCompletionSettings.body["model"] as? String
533-
val displayName = if (modelName != null) {
534-
"$serviceName ($modelName)"
535-
} else {
536-
serviceName
537-
}
538-
539-
ModelSelection(ServiceType.CUSTOM_OPENAI, serviceName, displayName)
540-
}
525+
val serviceId = service.id ?: return@mapNotNull null
526+
val serviceName = service.name ?: ""
527+
val modelName = service.chatCompletionSettings.body["model"] as? String
528+
val displayName = if (!modelName.isNullOrEmpty()) {
529+
if (modelName.length > 20) "$serviceName (...${modelName.takeLast(20)})" else "$serviceName ($modelName)"
530+
} else serviceName
531+
532+
ModelSelection(ServiceType.CUSTOM_OPENAI, serviceId, displayName)
541533
}
542534
} catch (e: Exception) {
543535
logger.error("Failed to get Custom OpenAI models", e)

0 commit comments

Comments
 (0)