fix: respect advanced settings (http/socks proxy) for agent mode
Some checks failed
Build / Build (push) Has been cancelled
Build / Verify Plugin (push) Has been cancelled

This commit is contained in:
Carl-Robert Linnupuu 2026-01-30 02:08:05 +00:00
parent e939d9dc00
commit e55778d668
4 changed files with 126 additions and 10 deletions

View file

@ -33,6 +33,7 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.agent.clients.CustomOpenAILLMClient
import ee.carlrobert.codegpt.agent.clients.HttpClientProvider
import ee.carlrobert.codegpt.agent.clients.InceptionAILLMClient
import ee.carlrobert.codegpt.agent.clients.ProxyAILLMClient
import ee.carlrobert.codegpt.agent.clients.RetryingPromptExecutor
@ -165,26 +166,28 @@ object AgentFactory {
}
fun createExecutor(provider: ServiceType, events: AgentEvents? = null): PromptExecutor {
val httpClient = HttpClientProvider.createHttpClient()
return when (provider) {
ServiceType.OPENAI -> {
val apiKey = getCredential(CredentialKey.OpenaiApiKey) ?: ""
createRetryingExecutor(OpenAILLMClient(apiKey), events)
createRetryingExecutor(OpenAILLMClient(apiKey, baseClient = httpClient), events)
}
ServiceType.ANTHROPIC -> {
val apiKey = getCredential(CredentialKey.AnthropicApiKey) ?: ""
createRetryingExecutor(AnthropicLLMClient(apiKey), events)
createRetryingExecutor(AnthropicLLMClient(apiKey, baseClient = httpClient), events)
}
ServiceType.GOOGLE -> {
val apiKey = getCredential(CredentialKey.GoogleApiKey) ?: ""
createRetryingExecutor(GoogleLLMClient(apiKey), events)
createRetryingExecutor(GoogleLLMClient(apiKey, baseClient = httpClient), events)
}
ServiceType.OLLAMA -> {
createRetryingExecutor(
OllamaClient(
baseUrl = service<OllamaSettings>().state.host ?: "http://localhost:11434"
baseUrl = service<OllamaSettings>().state.host ?: "http://localhost:11434",
baseClient = httpClient
),
events
)
@ -192,7 +195,7 @@ object AgentFactory {
ServiceType.MISTRAL -> {
val apiKey = getCredential(CredentialKey.MistralApiKey) ?: ""
createRetryingExecutor(MistralAILLMClient(apiKey), events)
createRetryingExecutor(MistralAILLMClient(apiKey, baseClient = httpClient), events)
}
ServiceType.CUSTOM_OPENAI -> {
@ -207,6 +210,7 @@ object AgentFactory {
CustomOpenAILLMClient.fromSettingsState(
apiKey,
state.chatCompletionSettings,
httpClient
),
events
)
@ -214,12 +218,12 @@ object AgentFactory {
ServiceType.PROXYAI -> {
val apiKey = getCredential(CredentialKey.CodeGptApiKey) ?: ""
createRetryingExecutor(ProxyAILLMClient(apiKey), events)
createRetryingExecutor(ProxyAILLMClient(apiKey, baseClient = httpClient), events)
}
ServiceType.INCEPTION -> {
val apiKey = getCredential(CredentialKey.InceptionApiKey) ?: ""
createRetryingExecutor(InceptionAILLMClient(apiKey), events)
createRetryingExecutor(InceptionAILLMClient(apiKey, baseClient = httpClient), events)
}
else -> throw UnsupportedOperationException("Provider not supported: $provider")

View file

@ -70,6 +70,7 @@ public class CustomOpenAILLMClient(
fun fromSettingsState(
apiKey: String,
state: CustomServiceChatCompletionSettingsState,
baseClient: HttpClient = HttpClient(),
timeoutConfig: ConnectionTimeoutConfig = ConnectionTimeoutConfig()
): CustomOpenAILLMClient {
val stateUrl = state.url ?: throw IllegalStateException("Url not set")
@ -80,7 +81,7 @@ public class CustomOpenAILLMClient(
chatCompletionsPath = uri.path,
timeoutConfig = timeoutConfig
)
return CustomOpenAILLMClient(apiKey, settings, state)
return CustomOpenAILLMClient(apiKey, settings, state, baseClient)
}
}

View file

@ -0,0 +1,106 @@
package ee.carlrobert.codegpt.agent.clients
import com.intellij.openapi.diagnostic.Logger
import com.intellij.util.net.ssl.CertificateManager
import ee.carlrobert.codegpt.settings.advanced.AdvancedSettings
import ee.carlrobert.codegpt.settings.advanced.AdvancedSettingsState
import io.ktor.client.*
import io.ktor.client.engine.apache5.*
import io.ktor.client.plugins.*
import org.apache.hc.client5.http.auth.AuthScope
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner
import org.apache.hc.core5.http.HttpHost
import java.net.Proxy
/**
* Provides configured Ktor HttpClient instances with proxy support for Agent mode.
*/
object HttpClientProvider {
private val logger = Logger.getInstance(HttpClientProvider::class.java)
/**
* Creates a Ktor HttpClient configured with proxy settings from AdvancedSettings.
* Supports both HTTP and SOCKS proxies with optional authentication.
*/
fun createHttpClient(): HttpClient {
val advancedSettings = AdvancedSettings.getCurrentState()
return HttpClient(Apache5) {
engine {
configureProxy(advancedSettings)
}
install(HttpTimeout) {
connectTimeoutMillis = advancedSettings.connectTimeout.toLong() * 1000
requestTimeoutMillis = advancedSettings.readTimeout.toLong() * 1000
socketTimeoutMillis = advancedSettings.readTimeout.toLong() * 1000
}
}
}
private fun Apache5EngineConfig.configureProxy(settings: AdvancedSettingsState) {
val proxyHost = settings.proxyHost
val proxyPort = settings.proxyPort
if (proxyHost.isBlank() || proxyPort == 0) {
logger.info("No proxy configured for Agent mode")
customizeClient {
sslContext = CertificateManager.getInstance().sslContext
}
return
}
logger.info("Configuring Agent mode with ${settings.proxyType} proxy: $proxyHost:$proxyPort")
val proxy = createProxy(settings) ?: run {
logger.warn("Invalid proxy type configured: ${settings.proxyType}")
customizeClient {
sslContext = CertificateManager.getInstance().sslContext
}
return
}
customizeClient {
sslContext = CertificateManager.getInstance().sslContext
setRoutePlanner(DefaultProxyRoutePlanner(proxy))
if (settings.isProxyAuthSelected) {
configureProxyAuthentication(settings, proxy, this)
}
}
}
private fun createProxy(settings: AdvancedSettingsState): HttpHost? {
return when (settings.proxyType) {
Proxy.Type.HTTP -> HttpHost("http", settings.proxyHost, settings.proxyPort)
Proxy.Type.SOCKS -> HttpHost("socks", settings.proxyHost, settings.proxyPort)
else -> null
}
}
private fun configureProxyAuthentication(
settings: AdvancedSettingsState,
proxy: HttpHost,
clientBuilder: HttpAsyncClientBuilder
) {
val username = settings.proxyUsername ?: ""
if (username.isNotBlank()) {
logger.info("Configuring proxy authentication for user: $username")
val credentialsProvider = BasicCredentialsProvider()
credentialsProvider.setCredentials(
AuthScope(proxy),
UsernamePasswordCredentials(
username,
settings.proxyPassword?.toCharArray() ?: charArrayOf()
)
)
clientBuilder.setDefaultCredentialsProvider(credentialsProvider)
}
}
}

View file

@ -6,13 +6,18 @@ import ai.koog.prompt.executor.clients.openai.OpenAIClientSettings
import ai.koog.prompt.executor.clients.openai.OpenAILLMClient
import ai.koog.prompt.llm.LLMProvider
import ai.koog.prompt.llm.LLModel
import io.ktor.client.*
class InceptionAILLMClient(apiKey: String) : OpenAILLMClient(
class InceptionAILLMClient(
apiKey: String,
baseClient: HttpClient = HttpClient()
) : OpenAILLMClient(
apiKey = apiKey,
settings = OpenAIClientSettings(
baseUrl = "https://api.inceptionlabs.ai",
chatCompletionsPath = "v1/chat/completions"
)
),
baseClient = baseClient
) {
data object Inception : LLMProvider("inception", "Inception")