ai-chat-android-auto / files / app / src / main / java / net / fuzzyworld / aichat / AnthropicProvider.kt
AnthropicProvider.kt
d522fe6d 2026-05-15 Pavel Safronov | 1 | package net.fuzzyworld.aichat |
449ce939 2026-05-14 Pavel Safronov | 2 | |
| 3 | import okhttp3.Call | |
| 4 | import okhttp3.Callback | |
| 5 | import okhttp3.MediaType.Companion.toMediaType | |
| 6 | import okhttp3.Request | |
| 7 | import okhttp3.RequestBody.Companion.toRequestBody | |
| 8 | import okhttp3.Response | |
| 9 | import org.json.JSONArray | |
| 10 | import org.json.JSONObject | |
| 11 | import java.io.IOException | |
| 12 | ||
| 13 | object 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 | } |