Kotlin / Android

Kotlin SDK

Integrate Meridian chat completions into your Android app using OkHttp and Kotlin coroutines. Minimal dependencies, maximum control.

Installation

Add the dependency to your module-level build.gradle.kts:

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    implementation("com.google.code.gson:gson:2.10.1")
}

Alternatively, use OpenAI-Kotlin for a higher-level client with full OpenAI-compatible API coverage.

Quickstart

Create a client, build a chat completion request, and stream the response using Kotlin coroutines.

import kotlinx.coroutines.*
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName

data class ChatMessage(
    val role: String,
    val content: String
)

data class ChatRequest(
    val model: String,
    val messages: List<ChatMessage>,
    val stream: Boolean = false
)

data class Choice(val message: ChatMessage)
data class ChatResponse(val choices: List<Choice>)

class MeridianClient(
    private val apiKey: String,
    private val baseUrl: String = "https://api.getnimbus.net/v1"
) {
    private val client = OkHttpClient()
    private val gson = Gson()
    private val jsonMediaType = "application/json".toMediaType()

    suspend fun chat(
        model: String,
        messages: List<ChatMessage>
    ): ChatResponse = withContext(Dispatchers.IO) {
        val body = gson.toJson(ChatRequest(model, messages))
            .toRequestBody(jsonMediaType)

        val request = Request.Builder()
            .url("$baseUrl/chat/completions")
            .addHeader("Authorization", "Bearer $apiKey")
            .post(body)
            .build()

        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) {
                throw Exception("API error: ${response.code}")
            }
            gson.fromJson(
                response.body?.string(),
                ChatResponse::class.java
            )
        }
    }
}

// Usage
fun main() = runBlocking {
    val meridian = MeridianClient("sk-your-key-here")
    val response = meridian.chat(
        model = "meridian-1",
        messages = listOf(
            ChatMessage("system", "You are a helpful assistant."),
            ChatMessage("user", "Explain Kotlin coroutines.")
        )
    )
    println(response.choices.first().message.content)
}

Streaming

Set stream = true and parse Server-Sent Events line by line.

suspend fun chatStream(
    model: String,
    messages: List<ChatMessage>,
    onDelta: (String) -> Unit
) = withContext(Dispatchers.IO) {
    val body = gson.toJson(
        ChatRequest(model, messages, stream = true)
    ).toRequestBody(jsonMediaType)

    val request = Request.Builder()
        .url("$baseUrl/chat/completions")
        .addHeader("Authorization", "Bearer $apiKey")
        .post(body)
        .build()

    client.newCall(request).execute().use { response ->
        val source = response.body?.source() ?: return@withContext
        while (!source.exhausted()) {
            val line = source.readUtf8Line() ?: continue
            if (line.startsWith("data: ") && line != "data: [DONE]") {
                val json = line.removePrefix("data: ")
                val delta = gson.fromJson(json, DeltaResponse::class.java)
                delta.choices.firstOrNull()
                    ?.delta?.content?.let(onDelta)
            }
        }
    }
}

data class DeltaContent(val content: String?)
data class DeltaChoice(val delta: DeltaContent)
data class DeltaResponse(val choices: List<DeltaChoice>)