fuzzyworld

ai-chat-android-auto / files / app / src / main / java / net / fuzzyworld / aichat / AnthropicProvider.kt

AnthropicProvider.kt

Blame: the commit that last changed each line · file · history

d522fe6d 2026-05-15 Pavel Safronov1package net.fuzzyworld.aichat
449ce939 2026-05-14 Pavel Safronov2
3import okhttp3.Call
4import okhttp3.Callback
5import okhttp3.MediaType.Companion.toMediaType
6import okhttp3.Request
7import okhttp3.RequestBody.Companion.toRequestBody
8import okhttp3.Response
9import org.json.JSONArray
10import org.json.JSONObject
11import java.io.IOException
12
13object AnthropicProvider : AiProvider {
14 override val id = "anthropic"
15 override val displayName = "Anthropic"
16 override val defaultBaseUrl = "https://api.anthropic.com"
17 override val defaultModel = "claude-sonnet-4-6"
18 override val requiresApiKey = true
19 override val allowsBaseUrlOverride = false
20 override val supportsWebSearch = true
21
22 override fun ask(
23 apiKey: String,
24 baseUrl: String,
25 model: String,
26 question: String,
27 onResult: (String?) -> Unit,
28 ) {
29 val payload = JSONObject().apply {
30 put("model", model)
31 put("max_tokens", 1024)
32 put("system", SYSTEM_PROMPT)
33 put("messages", JSONArray().apply {
34 put(JSONObject().apply {
35 put("role", "user")
36 put("content", question)
37 })
38 })
39 put("tools", JSONArray().apply {
40 put(JSONObject().apply {
41 put("type", "web_search_20250305")
42 put("name", "web_search")
43 })
44 })
45 }.toString().toRequestBody("application/json".toMediaType())
46
47 val request = Request.Builder()
48 .url("$baseUrl/v1/messages")
49 .post(payload)
50 .addHeader("x-api-key", apiKey)
51 .addHeader("anthropic-version", "2023-06-01")
52 .addHeader("anthropic-beta", "web-search-2025-03-05")
53 .build()
54
55 Http.client.newCall(request).enqueue(object : Callback {
56 override fun onFailure(call: Call, e: IOException) = onResult(null)
57 override fun onResponse(call: Call, response: Response) {
58 response.use {
59 if (!it.isSuccessful) { onResult(null); return }
60 try {
61 val content = JSONObject(it.body!!.string()).getJSONArray("content")
62 val text = buildString {
63 for (i in 0 until content.length()) {
64 val block = content.getJSONObject(i)
65 if (block.getString("type") == "text") append(block.getString("text"))
66 }
67 }.trim()
68 onResult(text.ifEmpty { null })
69 } catch (_: Exception) { onResult(null) }
70 }
71 }
72 })
73 }
74
75 override fun listModels(
76 apiKey: String,
77 baseUrl: String,
78 onResult: (List<ModelInfo>?) -> Unit,
79 ) {
80 val request = Request.Builder()
81 .url("$baseUrl/v1/models")
82 .get()
83 .addHeader("x-api-key", apiKey)
84 .addHeader("anthropic-version", "2023-06-01")
85 .build()
86
87 Http.client.newCall(request).enqueue(object : Callback {
88 override fun onFailure(call: Call, e: IOException) = onResult(null)
89 override fun onResponse(call: Call, response: Response) {
90 response.use {
91 if (!it.isSuccessful) { onResult(null); return }
92 try {
93 val data = JSONObject(it.body!!.string()).getJSONArray("data")
94 val models = (0 until data.length()).map { i ->
95 val obj = data.getJSONObject(i)
96 ModelInfo(
97 id = obj.getString("id"),
98 displayName = obj.optString("display_name", obj.getString("id")),
99 )
100 }
101 onResult(models)
102 } catch (_: Exception) { onResult(null) }
103 }
104 }
105 })
106 }
107}