Skip to content

Commit c5abcd9

Browse files
committed
feat(anthropic): basic implementation
1 parent 7ebd9d8 commit c5abcd9

File tree

12 files changed

+270
-7
lines changed

12 files changed

+270
-7
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ dependencies {
105105
// The Baidu Qianfan Large Model Platform, including the ERNIE series, can be accessed at https://docs.langchain4j.dev/integrations/language-models/qianfan/.
106106
implementation("dev.langchain4j:langchain4j-qianfan:0.33.0")
107107
implementation("dev.langchain4j:langchain4j-vertex-ai-gemini:0.33.0")
108+
implementation("dev.langchain4j:langchain4j-anthropic:0.33.0")
108109
// implementation("dev.langchain4j:langchain4j-hugging-face:0.28.0")
109110
// implementation("dev.langchain4j:langchain4j-milvus:0.28.0")
110111
// implementation("dev.langchain4j:langchain4j-local-ai:0.28.0")

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/Icons.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ object Icons {
88
val OLLAMA = IconLoader.getIcon("/icons/ollama15.svg", javaClass)
99
val QIANFAN = IconLoader.getIcon("/icons/qianfan.png", javaClass)
1010
val GEMINI = IconLoader.getIcon("/icons/gemini.png", javaClass)
11+
val ANTHROPIC = IconLoader.getIcon("/icons/anthropic.svg", javaClass)
1112
}

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/AppSettings2.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.github.blarc.ai.commits.intellij.plugin.AICommitsUtils.getCredentialA
55
import com.github.blarc.ai.commits.intellij.plugin.notifications.Notification
66
import com.github.blarc.ai.commits.intellij.plugin.notifications.sendNotification
77
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientConfiguration
8+
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic.AnthropicClientConfiguration
89
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.gemini.GeminiClientConfiguration
910
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.ollama.OllamaClientConfiguration
1011
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.openAi.OpenAiClientConfiguration
@@ -53,7 +54,8 @@ class AppSettings2 : PersistentStateComponent<AppSettings2> {
5354
OpenAiClientConfiguration::class,
5455
OllamaClientConfiguration::class,
5556
QianfanClientConfiguration::class,
56-
GeminiClientConfiguration::class
57+
GeminiClientConfiguration::class,
58+
AnthropicClientConfiguration::class,
5759
],
5860
style = XCollection.Style.v2
5961
)

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientTable.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.github.blarc.ai.commits.intellij.plugin.settings.clients
33
import com.github.blarc.ai.commits.intellij.plugin.AICommitsBundle.message
44
import com.github.blarc.ai.commits.intellij.plugin.createColumn
55
import com.github.blarc.ai.commits.intellij.plugin.settings.AppSettings2
6+
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic.AnthropicClientConfiguration
67
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.gemini.GeminiClientConfiguration
78
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.ollama.OllamaClientConfiguration
89
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.openAi.OpenAiClientConfiguration
@@ -142,7 +143,8 @@ class LLMClientTable {
142143
OpenAiClientConfiguration(),
143144
OllamaClientConfiguration(),
144145
QianfanClientConfiguration(),
145-
GeminiClientConfiguration()
146+
GeminiClientConfiguration(),
147+
AnthropicClientConfiguration()
146148
)
147149
} else {
148150
listOf(newLLMClientConfiguration)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic;
2+
3+
import com.github.blarc.ai.commits.intellij.plugin.Icons
4+
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientConfiguration
5+
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientSharedState
6+
import com.intellij.openapi.project.Project
7+
import com.intellij.openapi.vcs.ui.CommitMessage
8+
import com.intellij.util.xmlb.annotations.Attribute
9+
import com.intellij.util.xmlb.annotations.Transient
10+
import dev.langchain4j.model.anthropic.AnthropicChatModelName
11+
import javax.swing.Icon
12+
13+
class AnthropicClientConfiguration : LLMClientConfiguration(
14+
"Anthropic",
15+
AnthropicChatModelName.CLAUDE_2_1.toString(),
16+
"0.7"
17+
) {
18+
@Attribute
19+
var host: String = "https://api.anthropic.com/v1/"
20+
@Attribute
21+
var tokenIsStored: Boolean = false
22+
@Transient
23+
var token: String? = null
24+
@Transient
25+
var version: String? = null
26+
@Transient
27+
var beta: String? = null
28+
@Attribute
29+
var timeout: Int = 30
30+
31+
companion object {
32+
const val CLIENT_NAME = "Anthropic"
33+
}
34+
35+
override fun getClientName(): String {
36+
return CLIENT_NAME
37+
}
38+
39+
override fun getClientIcon(): Icon {
40+
return Icons.ANTHROPIC
41+
}
42+
43+
override fun getSharedState(): LLMClientSharedState {
44+
return AnthropicClientSharedState.getInstance()
45+
}
46+
47+
override fun generateCommitMessage(prompt: String, project: Project, commitMessage: CommitMessage) {
48+
return AnthropicClientService.getInstance().generateCommitMessage(this, prompt, project, commitMessage)
49+
}
50+
51+
override fun getRefreshModelsFunction() = null
52+
53+
override fun clone(): LLMClientConfiguration {
54+
val copy = AnthropicClientConfiguration()
55+
copy.id = id
56+
copy.name = name
57+
copy.modelId = modelId
58+
copy.temperature = temperature
59+
copy.tokenIsStored = tokenIsStored
60+
copy.version = version
61+
copy.beta = beta
62+
copy.timeout = timeout
63+
return copy
64+
}
65+
66+
override fun panel() = AnthropicClientPanel(this)
67+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic;
2+
3+
import com.github.blarc.ai.commits.intellij.plugin.AICommitsBundle.message
4+
import com.github.blarc.ai.commits.intellij.plugin.emptyText
5+
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientPanel
6+
import com.intellij.ui.components.JBPasswordField
7+
import com.intellij.ui.components.JBTextField
8+
import com.intellij.ui.dsl.builder.*
9+
10+
class AnthropicClientPanel private constructor(
11+
private val clientConfiguration: AnthropicClientConfiguration,
12+
val service: AnthropicClientService
13+
) : LLMClientPanel(clientConfiguration) {
14+
private val tokenPasswordField = JBPasswordField()
15+
private val versionTextField = JBTextField()
16+
private val betaTextField = JBTextField()
17+
18+
constructor(configuration: AnthropicClientConfiguration) : this(configuration, AnthropicClientService.getInstance())
19+
20+
override fun create() = panel {
21+
nameRow()
22+
hostRow(clientConfiguration::host.toNullableProperty())
23+
modelIdRow()
24+
temperatureRow()
25+
timeoutRow(clientConfiguration::timeout)
26+
tokenRow()
27+
versionRow()
28+
betaRow()
29+
verifyRow()
30+
}
31+
32+
private fun Panel.tokenRow() {
33+
row {
34+
label(message("settings.llmClient.token"))
35+
.widthGroup("label")
36+
cell(tokenPasswordField)
37+
.bindText(getter = { "" }, setter = {
38+
AnthropicClientService.getInstance().saveToken(clientConfiguration, it)
39+
})
40+
.emptyText(if (clientConfiguration.tokenIsStored) message("settings.llmClient.token.stored") else message("settings.anthropic.token.example"))
41+
.resizableColumn()
42+
.align(Align.FILL)
43+
// maxLineLength was eye-balled, but prevents the dialog getting wider
44+
.comment(message("settings.anthropic.token.comment"), 50)
45+
}
46+
}
47+
48+
private fun Panel.versionRow() {
49+
row {
50+
label(message("settings.anthropic.version"))
51+
.widthGroup("label")
52+
cell(versionTextField)
53+
.bindText(clientConfiguration::version.toNonNullableProperty(""))
54+
.resizableColumn()
55+
.align(Align.FILL)
56+
.comment(message("settings.anthropic.version.comment"), 50)
57+
}
58+
}
59+
60+
private fun Panel.betaRow() {
61+
row {
62+
label(message("settings.anthropic.beta"))
63+
.widthGroup("label")
64+
cell(betaTextField)
65+
.bindText(clientConfiguration::beta.toNonNullableProperty(""))
66+
.resizableColumn()
67+
.align(Align.FILL)
68+
.comment(message("settings.anthropic.beta.comment"), 50)
69+
}
70+
}
71+
72+
override fun verifyConfiguration() {
73+
// Configuration passed to panel is already a copy of the original or a new configuration
74+
clientConfiguration.host = hostComboBox.item
75+
clientConfiguration.modelId = modelComboBox.item
76+
clientConfiguration.temperature = temperatureTextField.text
77+
clientConfiguration.token = String(tokenPasswordField.password)
78+
clientConfiguration.timeout = socketTimeoutTextField.text.toInt()
79+
clientConfiguration.version = versionTextField.text
80+
clientConfiguration.beta = betaTextField.text
81+
82+
service.verifyConfiguration(clientConfiguration, verifyLabel)
83+
}
84+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic;
2+
3+
import com.github.blarc.ai.commits.intellij.plugin.AICommitsUtils.getCredentialAttributes
4+
import com.github.blarc.ai.commits.intellij.plugin.AICommitsUtils.retrieveToken
5+
import com.github.blarc.ai.commits.intellij.plugin.notifications.Notification
6+
import com.github.blarc.ai.commits.intellij.plugin.notifications.sendNotification
7+
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientService
8+
import com.intellij.ide.passwordSafe.PasswordSafe
9+
import com.intellij.openapi.components.Service
10+
import com.intellij.openapi.components.service
11+
import com.intellij.util.text.nullize
12+
import dev.langchain4j.model.anthropic.AnthropicChatModel
13+
import dev.langchain4j.model.chat.ChatLanguageModel
14+
import kotlinx.coroutines.CoroutineScope
15+
import kotlinx.coroutines.Dispatchers
16+
import kotlinx.coroutines.launch
17+
import java.time.Duration
18+
19+
@Service(Service.Level.APP)
20+
class AnthropicClientService(private val cs: CoroutineScope) : LLMClientService<AnthropicClientConfiguration>(cs) {
21+
22+
companion object {
23+
@JvmStatic
24+
fun getInstance(): AnthropicClientService = service()
25+
}
26+
27+
override suspend fun buildChatModel(client: AnthropicClientConfiguration): ChatLanguageModel {
28+
val token = client.token.nullize(true) ?: retrieveToken(client.id)?.toString(true)
29+
val builder = AnthropicChatModel.builder()
30+
.modelName(client.modelId)
31+
.temperature(client.temperature.toDouble())
32+
.apiKey(token ?: "")
33+
.baseUrl(client.host)
34+
.timeout(Duration.ofSeconds(client.timeout.toLong()))
35+
36+
client.version?.takeIf { it.isNotBlank() }?.let {
37+
builder.version(it)
38+
}
39+
40+
client.beta?.takeIf { it.isNotBlank() }?.let {
41+
builder.beta(it)
42+
}
43+
44+
return builder.build()
45+
46+
}
47+
48+
fun saveToken(client: AnthropicClientConfiguration, token: String) {
49+
cs.launch(Dispatchers.Default) {
50+
try {
51+
PasswordSafe.instance.setPassword(getCredentialAttributes(client.id), token)
52+
client.tokenIsStored = true
53+
} catch (e: Exception) {
54+
sendNotification(Notification.unableToSaveToken(e.message))
55+
}
56+
}
57+
}
58+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic
2+
3+
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientSharedState
4+
import com.intellij.openapi.components.*
5+
import com.intellij.util.xmlb.annotations.XCollection
6+
import dev.langchain4j.model.anthropic.AnthropicChatModelName
7+
8+
@Service(Service.Level.APP)
9+
@State(name = "AnthropicClientSharedState", storages = [Storage("AICommitsAnthropic.xml")])
10+
class AnthropicClientSharedState : PersistentStateComponent<AnthropicClientSharedState>, LLMClientSharedState {
11+
12+
companion object {
13+
@JvmStatic
14+
fun getInstance(): AnthropicClientSharedState = service()
15+
}
16+
17+
@XCollection(style = XCollection.Style.v2)
18+
override val hosts = mutableSetOf("https://api.anthropic.com/v1/")
19+
20+
@XCollection(style = XCollection.Style.v2)
21+
override val modelIds: MutableSet<String> = AnthropicChatModelName.entries.stream()
22+
.map { it.toString() }
23+
.toList()
24+
.toMutableSet()
25+
26+
override fun getState(): AnthropicClientSharedState = this
27+
28+
override fun loadState(state: AnthropicClientSharedState) {
29+
modelIds += state.modelIds
30+
hosts += state.hosts
31+
}
32+
}

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/openAi/OpenAiClientPanel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class OpenAiClientPanel(private val clientConfiguration: OpenAiClientConfigurati
4444
.bindText(getter = { "" }, setter = {
4545
OpenAiClientService.getInstance().saveToken(clientConfiguration, it)
4646
})
47-
.emptyText(if (clientConfiguration.tokenIsStored) message("settings.openAI.token.stored") else message("settings.openAI.token.example"))
47+
.emptyText(if (clientConfiguration.tokenIsStored) message("settings.llmClient.token.stored") else message("settings.openAI.token.example"))
4848
.resizableColumn()
4949
.align(Align.FILL)
5050
// maxLineLength was eye-balled, but prevents the dialog getting wider

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/qianfan/QianfanClientPanel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class QianfanClientPanel(private val clientConfiguration: QianfanClientConfigura
2525
.bindText(getter = {""}, setter = {
2626
QianfanClientService.getInstance().saveApiKey(clientConfiguration, it)
2727
})
28-
.emptyText(if (clientConfiguration.apiKeyIsStored) message("settings.openAI.token.stored") else "JzRxxxxxxxxxxxxxxxxxxxxx")
28+
.emptyText(if (clientConfiguration.apiKeyIsStored) message("settings.llmClient.token.stored") else "JzRxxxxxxxxxxxxxxxxxxxxx")
2929
.resizableColumn()
3030
.align(Align.FILL)
3131
}
@@ -36,7 +36,7 @@ class QianfanClientPanel(private val clientConfiguration: QianfanClientConfigura
3636
.bindText(getter = {""}, setter = {
3737
QianfanClientService.getInstance().saveSecretKey(clientConfiguration, it)
3838
})
39-
.emptyText(if (clientConfiguration.secretKeyIsStored) message("settings.openAI.token.stored") else "kSlxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
39+
.emptyText(if (clientConfiguration.secretKeyIsStored) message("settings.llmClient.token.stored") else "kSlxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
4040
.resizableColumn()
4141
.align(Align.FILL)
4242
}

0 commit comments

Comments
 (0)