ai-chat-android-auto / files / app / src / main / java / net / fuzzyworld / aichat / GeminiProvider.kt
GeminiProvider.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 GeminiProvider : AiProvider { | |
| 14 | override val id = "gemini" | |
| 15 | override val displayName = "Google Gemini" | |
| 16 | override val defaultBaseUrl = "https://generativelanguage.googleapis.com" | |
| 17 | override val defaultModel = "gemini-2.0-flash" | |
| 18 | override val requiresApiKey = true | |
| 19 | override val allowsBaseUrlOverride = false | |
| 20 | override val supportsWebSearch = false | |
| 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("system_instruction", JSONObject().apply { | |
| 31 | put("parts", JSONArray().apply { | |
| 32 | put(JSONObject().apply { put("text", SYSTEM_PROMPT) }) | |
| 33 | }) | |
| 34 | }) | |
| 35 | put("contents", JSONArray().apply { | |
| 36 | put(JSONObject().apply { | |
| 37 | put("role", "user") | |
| 38 | put("parts", JSONArray().apply { | |
| 39 | put(JSONObject().apply { put("text", question) }) | |
| 40 | }) | |
| 41 | }) | |
| 42 | }) | |
| 43 | }.toString().toRequestBody("application/json".toMediaType()) | |
| 44 | ||
| 45 | val request = Request.Builder() | |
| 46 | .url("$baseUrl/v1beta/models/$model:generateContent?key=$apiKey") | |
| 47 | .post(payload) | |
| 48 | .build() | |
| 49 | ||
| 50 | Http.client.newCall(request).enqueue(object : Callback { | |
| 51 | override fun onFailure(call: Call, e: IOException) = onResult(null) | |
| 52 | override fun onResponse(call: Call, response: Response) { | |
| 53 | response.use { | |
| 54 | if (!it.isSuccessful) { onResult(null); return } | |
| 55 | try { | |
| 56 | val text = JSONObject(it.body!!.string()) | |
| 57 | .getJSONArray("candidates") | |
| 58 | .getJSONObject(0) | |
| 59 | .getJSONObject("content") | |
| 60 | .getJSONArray("parts") | |
| 61 | .getJSONObject(0) | |
| 62 | .getString("text") | |
| 63 | onResult(text.trim().ifEmpty { null }) | |
| 64 | } catch (_: Exception) { onResult(null) } | |
| 65 | } | |
| 66 | } | |
| 67 | }) | |
| 68 | } | |
| 69 | ||
| 70 | override fun listModels( | |
| 71 | apiKey: String, | |
| 72 | baseUrl: String, | |
| 73 | onResult: (List<ModelInfo>?) -> Unit, | |
| 74 | ) { | |
| 75 | val request = Request.Builder() | |
| 76 | .url("$baseUrl/v1beta/models?key=$apiKey") | |
| 77 | .get() | |
| 78 | .build() | |
| 79 | ||
| 80 | Http.client.newCall(request).enqueue(object : Callback { | |
| 81 | override fun onFailure(call: Call, e: IOException) = onResult(null) | |
| 82 | override fun onResponse(call: Call, response: Response) { | |
| 83 | response.use { | |
| 84 | if (!it.isSuccessful) { onResult(null); return } | |
| 85 | try { | |
| 86 | val arr = JSONObject(it.body!!.string()).getJSONArray("models") | |
| 87 | val models = (0 until arr.length()) | |
| 88 | .map { i -> arr.getJSONObject(i) } | |
| 89 | .filter { obj -> | |
| 90 | val methods = obj.optJSONArray("supportedGenerationMethods") | |
| 91 | methods != null && (0 until methods.length()).any { j -> | |
| 92 | methods.getString(j) == "generateContent" | |
| 93 | } | |
| 94 | } | |
| 95 | .map { obj -> | |
| 96 | // "name" is "models/gemini-2.0-flash" — strip the prefix | |
| 97 | val mid = obj.getString("name").removePrefix("models/") | |
| 98 | ModelInfo( | |
| 99 | id = mid, | |
| 100 | displayName = obj.optString("displayName", mid), | |
| 101 | ) | |
| 102 | } | |
| 103 | onResult(models) | |
| 104 | } catch (_: Exception) { onResult(null) } | |
| 105 | } | |
| 106 | } | |
| 107 | }) | |
| 108 | } | |
| 109 | } |