refactor: remove extra context length state
Some checks failed
Build / Build (push) Failing after 5s
Build / Verify Plugin (push) Has been skipped

This commit is contained in:
Carl-Robert Linnupuu 2026-03-17 16:01:01 +00:00
parent 0d83e32ab1
commit 6a53318fea
9 changed files with 161 additions and 52 deletions

View file

@ -21,6 +21,7 @@ import ee.carlrobert.codegpt.completions.llama.LlamaModel
import ee.carlrobert.codegpt.settings.models.ModelCatalog.Companion.MERCURY_CODER
import ee.carlrobert.codegpt.settings.service.FeatureType
import ee.carlrobert.codegpt.settings.service.ServiceType
import ee.carlrobert.codegpt.settings.service.custom.DEFAULT_CUSTOM_OPENAI_MAX_OUTPUT_TOKENS
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettingsState
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings
@ -749,7 +750,17 @@ private class CustomOpenAIModelProvider : ModelProvider {
LLMCapability.MultipleChoices,
) + additionalOpenAICapability,
contextLength = svc.contextWindowSize.coerceAtLeast(1L),
maxOutputTokens = svc.maxOutputTokens.coerceAtLeast(1L)
maxOutputTokens = svc.chatCompletionSettings.body.entries
.firstNotNullOfOrNull { (key, value) ->
when (key) {
"max_completion_tokens", "max_tokens", "max_output_tokens" ->
(value as? Number)?.toLong() ?: value.toString()
.toLongOrNull()
else -> null
}
}
?: DEFAULT_CUSTOM_OPENAI_MAX_OUTPUT_TOKENS
),
displayName = displayName,
serviceId = serviceId,

View file

@ -60,14 +60,18 @@ class CustomServicesSettings :
val oldSettingsService = serviceOrNull<CustomServiceSettings>()
// This line checks if the legacy API key exists to determine if migration of old settings is needed
val oldApiKey = CredentialsStore.getCredential(CredentialsStore.CredentialKey.CustomServiceApiKeyLegacy)
val oldApiKey =
CredentialsStore.getCredential(CredentialsStore.CredentialKey.CustomServiceApiKeyLegacy)
if (oldSettingsService != null && oldApiKey != null) {
val migrated = CustomServiceSettingsState().apply { copyFrom(oldSettingsService.state) }
state.services.clear()
state.services.add(migrated)
CredentialsStore.setCredential(CredentialsStore.CredentialKey.CustomServiceApiKeyLegacy, null)
CredentialsStore.setCredential(
CredentialsStore.CredentialKey.CustomServiceApiKeyLegacy,
null
)
oldSettingsService.state.apply {
template = CustomServiceTemplate.OPENAI
@ -156,7 +160,6 @@ class CustomServiceSettingsState : BaseState() {
var name by string(DEFAULT_SERVICE_SETTINGS_NAME)
var template by enum(CustomServiceTemplate.OPENAI)
var contextWindowSize by property(DEFAULT_CUSTOM_OPENAI_CONTEXT_WINDOW_SIZE)
var maxOutputTokens by property(DEFAULT_CUSTOM_OPENAI_MAX_OUTPUT_TOKENS)
var chatCompletionSettings by property(CustomServiceChatCompletionSettingsState())
var codeCompletionSettings by property(CustomServiceCodeCompletionSettingsState())

View file

@ -23,7 +23,6 @@ import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.credentials.CredentialsStore
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey
import ee.carlrobert.codegpt.settings.service.custom.DEFAULT_CUSTOM_OPENAI_CONTEXT_WINDOW_SIZE
import ee.carlrobert.codegpt.settings.service.custom.DEFAULT_CUSTOM_OPENAI_MAX_OUTPUT_TOKENS
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettingsState
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
import ee.carlrobert.codegpt.settings.service.custom.form.model.CustomServiceSettingsData
@ -130,8 +129,6 @@ class CustomServiceForm(
}
private val contextWindowSizeSpinner =
createTokenSpinner(DEFAULT_CUSTOM_OPENAI_CONTEXT_WINDOW_SIZE)
private val maxOutputTokensSpinner =
createTokenSpinner(DEFAULT_CUSTOM_OPENAI_MAX_OUTPUT_TOKENS)
private val templateHelpText = JBLabel(General.ContextHelp)
private val templateComboBox = ComboBox(EnumComboBoxModel(CustomServiceTemplate::class.java))
private val chatCompletionsForm: CustomServiceChatCompletionForm
@ -225,7 +222,6 @@ class CustomServiceForm(
apiKeyField.text = selectedItem.apiKey ?: ""
nameField.text = selectedItem.name
contextWindowSizeSpinner.value = selectedItem.contextWindowSize
maxOutputTokensSpinner.value = selectedItem.maxOutputTokens
templateComboBox.selectedItem = selectedItem.template
updateTemplateHelpTextTooltip(selectedItem.template)
} finally {
@ -246,10 +242,6 @@ class CustomServiceForm(
contextWindowSizeSpinner,
editedItem.contextWindowSize
),
maxOutputTokens = readSpinnerValue(
maxOutputTokensSpinner,
editedItem.maxOutputTokens
),
chatCompletionSettings = editedItem.chatCompletionSettings.copy(
url = chatCompletionsForm.url,
body = chatCompletionsForm.body,
@ -462,12 +454,11 @@ class CustomServiceForm(
UIUtil.createComment("settingsConfigurable.service.custom.openai.apiKey.comment")
)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.contextWindowSize.label"),
contextWindowSizeSpinner
)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.maxOutputTokens.label"),
maxOutputTokensSpinner
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.contextLength.label"),
JPanel(FlowLayout(FlowLayout.LEADING, 0, 0)).apply {
isOpaque = false
add(contextWindowSizeSpinner)
}
)
.addVerticalGap(4)
.addComponent(tabbedPane)
@ -654,7 +645,7 @@ class CustomServiceForm(
private fun createTokenSpinner(initialValue: Long): JSpinner =
JSpinner(SpinnerNumberModel(initialValue, 1L, Long.MAX_VALUE, 1L)).apply {
(editor as? JSpinner.DefaultEditor)?.textField?.columns = 30
(editor as? JSpinner.DefaultEditor)?.textField?.columns = 10
}
private fun readSpinnerValue(spinner: JSpinner, fallback: Long): Long {

View file

@ -1,7 +1,6 @@
package ee.carlrobert.codegpt.settings.service.custom.form.model
import ee.carlrobert.codegpt.settings.service.custom.DEFAULT_CUSTOM_OPENAI_CONTEXT_WINDOW_SIZE
import ee.carlrobert.codegpt.settings.service.custom.DEFAULT_CUSTOM_OPENAI_MAX_OUTPUT_TOKENS
import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceTemplate
import java.util.UUID
@ -11,7 +10,6 @@ data class CustomServiceSettingsData(
val template: CustomServiceTemplate,
val apiKey: String?,
val contextWindowSize: Long = DEFAULT_CUSTOM_OPENAI_CONTEXT_WINDOW_SIZE,
val maxOutputTokens: Long = DEFAULT_CUSTOM_OPENAI_MAX_OUTPUT_TOKENS,
val chatCompletionSettings: CustomServiceChatCompletionSettingsData,
val codeCompletionSettings: CustomServiceCodeCompletionSettingsData
)

View file

@ -12,7 +12,6 @@ fun CustomServiceSettingsData.mapToState(): CustomServiceSettingsState =
serviceState.name = name
serviceState.template = template
serviceState.contextWindowSize = contextWindowSize
serviceState.maxOutputTokens = maxOutputTokens
serviceState.chatCompletionSettings = chatCompletionSettings.mapToState()
serviceState.codeCompletionSettings = codeCompletionSettings.mapToState()
}

View file

@ -19,7 +19,6 @@ fun CustomServiceSettingsState.mapToData(): CustomServiceSettingsData =
CredentialsStore.getCredential(CredentialsStore.CredentialKey.CustomServiceApiKeyById(id!!))
else null,
contextWindowSize = contextWindowSize,
maxOutputTokens = maxOutputTokens,
chatCompletionSettings = chatCompletionSettings.mapToData(),
codeCompletionSettings = codeCompletionSettings.mapToData()
)

View file

@ -183,8 +183,7 @@ settingsConfigurable.service.custom.openai.importSettings=Import settings...
settingsConfigurable.service.custom.openai.exportSettings=Export settings
settingsConfigurable.service.custom.openai.marketing.text=Handling sensitive data? Connect to self-hosted models and manage configuration centrally with a private, configurable ProxyAI extension.
settingsConfigurable.service.custom.openai.marketing.learnMore=Learn more about our custom extension
settingsConfigurable.service.custom.openai.contextWindowSize.label=Context window size:
settingsConfigurable.service.custom.openai.maxOutputTokens.label=Max output tokens:
settingsConfigurable.service.custom.openai.contextLength.label=Context length:
settingsConfigurable.prompts.import=Import settings...
settingsConfigurable.prompts.export=Export settings
settingsConfigurable.prompts.exportDialog.saveTo=Save to:

View file

@ -178,8 +178,7 @@ settingsConfigurable.service.custom.openai.connectionSuccess=\u8FDE\u63A5\u6210\
settingsConfigurable.service.custom.openai.connectionFailed=\u8FDE\u63A5\u5931\u8D25\u3002
settingsConfigurable.service.custom.openai.importSettings=\u5BFC\u5165\u8BBE\u7F6E...
settingsConfigurable.service.custom.openai.exportSettings=\u5BFC\u51FA\u8BBE\u7F6E
settingsConfigurable.service.custom.openai.contextWindowSize.label=\u4E0A\u4E0B\u6587\u7A97\u53E3\u5927\u5C0F:
settingsConfigurable.service.custom.openai.maxOutputTokens.label=\u6700\u5927\u8F93\u51FA\u6807\u8BB0\u6570:
settingsConfigurable.service.custom.openai.contextLength.label=\u4E0A\u4E0B\u6587\u957F\u5EA6:
settingsConfigurable.prompts.import=\u5BFC\u5165\u8BBE\u7F6E...
settingsConfigurable.prompts.export=\u5BFC\u51FA\u8BBE\u7F6E
settingsConfigurable.prompts.exportDialog.saveTo=\u4FDD\u5B58\u5230:

View file

@ -32,31 +32,102 @@ class ModelSettingsTest : IntegrationTest() {
connection = ApplicationManager.getApplication().messageBus.connect()
connection.subscribe(ModelChangeNotifier.getTopic(), object : ModelChangeNotifier {
override fun agentModelChanged(newModel: String, serviceType: ServiceType) {
lastNotification.set(NotificationData(FeatureType.AGENT, newModel, serviceType, "agent"))
lastNotification.set(
NotificationData(
FeatureType.AGENT,
newModel,
serviceType,
"agent"
)
)
}
override fun chatModelChanged(newModel: String, serviceType: ServiceType) {
lastNotification.set(NotificationData(FeatureType.CHAT, newModel, serviceType, "chat"))
lastNotification.set(
NotificationData(
FeatureType.CHAT,
newModel,
serviceType,
"chat"
)
)
}
override fun codeModelChanged(newModel: String, serviceType: ServiceType) {
lastNotification.set(NotificationData(FeatureType.CODE_COMPLETION, newModel, serviceType, "code"))
lastNotification.set(
NotificationData(
FeatureType.CODE_COMPLETION,
newModel,
serviceType,
"code"
)
)
}
override fun autoApplyModelChanged(newModel: String, serviceType: ServiceType) {
lastNotification.set(NotificationData(FeatureType.AUTO_APPLY, newModel, serviceType, "autoApply"))
lastNotification.set(
NotificationData(
FeatureType.AUTO_APPLY,
newModel,
serviceType,
"autoApply"
)
)
}
override fun commitMessageModelChanged(newModel: String, serviceType: ServiceType) {
lastNotification.set(NotificationData(FeatureType.COMMIT_MESSAGE, newModel, serviceType, "commitMessage"))
lastNotification.set(
NotificationData(
FeatureType.COMMIT_MESSAGE,
newModel,
serviceType,
"commitMessage"
)
)
}
override fun inlineEditModelChanged(newModel: String, serviceType: ServiceType) {
lastNotification.set(NotificationData(FeatureType.INLINE_EDIT, newModel, serviceType, "inlineEdit"))
lastNotification.set(
NotificationData(
FeatureType.INLINE_EDIT,
newModel,
serviceType,
"inlineEdit"
)
)
}
override fun nextEditModelChanged(newModel: String, serviceType: ServiceType) {
lastNotification.set(NotificationData(FeatureType.NEXT_EDIT, newModel, serviceType, "nextEdit"))
lastNotification.set(
NotificationData(
FeatureType.NEXT_EDIT,
newModel,
serviceType,
"nextEdit"
)
)
}
override fun nameLookupModelChanged(newModel: String, serviceType: ServiceType) {
lastNotification.set(NotificationData(FeatureType.LOOKUP, newModel, serviceType, "lookup"))
lastNotification.set(
NotificationData(
FeatureType.LOOKUP,
newModel,
serviceType,
"lookup"
)
)
}
override fun modelChanged(featureType: FeatureType, newModel: String, serviceType: ServiceType) {
lastNotification.compareAndSet(null, NotificationData(featureType, newModel, serviceType, "general"))
override fun modelChanged(
featureType: FeatureType,
newModel: String,
serviceType: ServiceType
) {
lastNotification.compareAndSet(
null,
NotificationData(featureType, newModel, serviceType, "general")
)
}
})
}
@ -69,7 +140,7 @@ class ModelSettingsTest : IntegrationTest() {
fun `test setModelWithProvider with new model triggers change notification`() {
modelSettings.setModelWithProvider(FeatureType.CHAT, "gpt-3.5-turbo", ServiceType.OPENAI)
lastNotification.set(null)
modelSettings.setModelWithProvider(FeatureType.CHAT, "gpt-4o", ServiceType.OPENAI)
val notification = lastNotification.get()
@ -90,10 +161,18 @@ class ModelSettingsTest : IntegrationTest() {
}
fun `test setModelWithProvider with code completion triggers code model notification`() {
modelSettings.setModelWithProvider(FeatureType.CODE_COMPLETION, "gpt-3.5-turbo-instruct", ServiceType.OPENAI)
modelSettings.setModelWithProvider(
FeatureType.CODE_COMPLETION,
"gpt-3.5-turbo-instruct",
ServiceType.OPENAI
)
lastNotification.set(null)
modelSettings.setModelWithProvider(FeatureType.CODE_COMPLETION, "qwen-2.5-32b-code", ServiceType.PROXYAI)
modelSettings.setModelWithProvider(
FeatureType.CODE_COMPLETION,
"qwen-2.5-32b-code",
ServiceType.PROXYAI
)
val notification = lastNotification.get()
assertThat(notification!!.featureType).isEqualTo(FeatureType.CODE_COMPLETION)
@ -103,7 +182,7 @@ class ModelSettingsTest : IntegrationTest() {
fun `test setModelWithProvider with auto apply triggers auto apply notification`() {
lastNotification.set(null)
modelSettings.setModelWithProvider(FeatureType.AUTO_APPLY, "gpt-4.1", ServiceType.PROXYAI)
val notification = lastNotification.get()
@ -114,8 +193,12 @@ class ModelSettingsTest : IntegrationTest() {
fun `test setModelWithProvider with commit message triggers commit message notification`() {
lastNotification.set(null)
modelSettings.setModelWithProvider(FeatureType.COMMIT_MESSAGE, "deepseek-v3", ServiceType.PROXYAI)
modelSettings.setModelWithProvider(
FeatureType.COMMIT_MESSAGE,
"deepseek-v3",
ServiceType.PROXYAI
)
val notification = lastNotification.get()
assertThat(notification!!.featureType).isEqualTo(FeatureType.COMMIT_MESSAGE)
@ -125,8 +208,12 @@ class ModelSettingsTest : IntegrationTest() {
fun `test setModelWithProvider with edit code triggers edit code notification`() {
lastNotification.set(null)
modelSettings.setModelWithProvider(FeatureType.INLINE_EDIT, "claude-4-5-sonnet", ServiceType.PROXYAI)
modelSettings.setModelWithProvider(
FeatureType.INLINE_EDIT,
"claude-4-5-sonnet",
ServiceType.PROXYAI
)
val notification = lastNotification.get()
assertThat(notification!!.featureType).isEqualTo(FeatureType.INLINE_EDIT)
@ -137,8 +224,12 @@ class ModelSettingsTest : IntegrationTest() {
fun `test setModelWithProvider with next edit triggers next edit notification`() {
modelSettings.state.modelSelections.remove("NEXT_EDIT")
lastNotification.set(null)
modelSettings.setModelWithProvider(FeatureType.NEXT_EDIT, "mercury-coder", ServiceType.PROXYAI)
modelSettings.setModelWithProvider(
FeatureType.NEXT_EDIT,
"mercury-coder",
ServiceType.PROXYAI
)
val notification = lastNotification.get()
assertThat(notification!!.featureType).isEqualTo(FeatureType.NEXT_EDIT)
@ -149,7 +240,7 @@ class ModelSettingsTest : IntegrationTest() {
fun `test setModelWithProvider with lookup triggers lookup notification`() {
modelSettings.setModelWithProvider(FeatureType.LOOKUP, "deepseek-v3", ServiceType.PROXYAI)
lastNotification.set(null)
modelSettings.setModelWithProvider(FeatureType.LOOKUP, "gpt-4.1-mini", ServiceType.PROXYAI)
val notification = lastNotification.get()
@ -160,7 +251,7 @@ class ModelSettingsTest : IntegrationTest() {
fun `test setModel delegates to setModelWithProvider`() {
lastNotification.set(null)
modelSettings.setModel(FeatureType.CHAT, "gpt-4o", ServiceType.OPENAI)
val notification = lastNotification.get()
@ -249,7 +340,26 @@ class ModelSettingsTest : IntegrationTest() {
assertThat(selection.llmModel.supports(LLMCapability.OpenAIEndpoint.Responses)).isFalse()
}
fun `test custom openai responses agent model uses configured token limits`() {
fun `test custom openai agent model uses max_completion_tokens from request body`() {
val customServicesSettings = service<CustomServicesSettings>()
val customService = createCustomService(
name = "Custom Agent",
model = "custom-agent-model",
contextWindowSize = 256_000L,
maxOutputTokens = 48_000L
)
customServicesSettings.state.services.clear()
customServicesSettings.state.services.add(customService)
val selection = modelSettings.getAvailableModels(FeatureType.AGENT)
.single { it.id == customService.id }
assertThat(selection.llmModel.contextLength).isEqualTo(256_000L)
assertThat(selection.llmModel.maxOutputTokens).isEqualTo(48_000L)
assertThat(selection.llmModel.supports(LLMCapability.OpenAIEndpoint.Responses)).isFalse()
}
fun `test custom openai responses agent model uses request body token limits`() {
val customServicesSettings = service<CustomServicesSettings>()
val customService = createCustomService(
name = "Custom Responses",
@ -312,10 +422,10 @@ class ModelSettingsTest : IntegrationTest() {
return CustomServiceSettingsState().apply {
this.name = name
this.contextWindowSize = contextWindowSize
this.maxOutputTokens = maxOutputTokens
chatCompletionSettings.url = path
chatCompletionSettings.body.clear()
chatCompletionSettings.body["model"] = model
chatCompletionSettings.body["max_output_tokens"] = maxOutputTokens
}
}
}