---
title: "Kotlin SDK"
canonical: https://docs.qvac.tether.io/sdk/kotlin-sdk/
collection: "SDK"
package: "@qvac/sdk"
line: v0.23
current_line: true
---

# Kotlin SDK (/sdk/kotlin-sdk)



## Overview

The Kotlin SDK (`io.tether:qvac-sdk-kotlin`) is the Kotlin client for QVAC. It
drives the same worker contract as the JS/TS and Python clients, and provides
Kotlin data classes, `Flow` streams, structured completion runs, and an
isolated Android service host.

## Requirements

* Android: `arm64-v8a`, API 29 or newer.
* Desktop JVM: experimental. The client resolves and starts a local `@qvac/sdk`
  worker.
* A Kotlin 2.3 or newer compiler. The artifacts depend on
  [`bare-rpc-kotlin`](https://github.com/holepunchto/bare-rpc-kotlin), which is
  compiled with Kotlin 2.4.

Kotlin/Native targets are not published.

## Install

The Kotlin SDK is not published to a public Maven registry. Each SDK release on
GitHub carries it as Maven repository archives that you download and point
Gradle at. There is one archive per Android runtime profile:

| Archive                                       | Dependency                             | Capabilities                                                  |
| --------------------------------------------- | -------------------------------------- | ------------------------------------------------------------- |
| `qvac-sdk-android-0.23.0-maven.zip`           | `io.tether:qvac-sdk-android`           | all current SDK addons/capabilities                           |
| `qvac-sdk-android-assistant-0.23.0-maven.zip` | `io.tether:qvac-sdk-android-assistant` | chat and transcription                                        |
| `qvac-sdk-android-llm-0.23.0-maven.zip`       | `io.tether:qvac-sdk-android-llm`       | completion and embeddings                                     |
| `qvac-sdk-android-speech-0.23.0-maven.zip`    | `io.tether:qvac-sdk-android-speech`    | Whisper, Parakeet and BCI transcription, translation, and TTS |
| `qvac-sdk-android-vision-0.23.0-maven.zip`    | `io.tether:qvac-sdk-android-vision`    | multimodal completion, OCR, and classification                |
| `qvac-sdk-android-media-0.23.0-maven.zip`     | `io.tether:qvac-sdk-android-media`     | audio, image, video, world generation, and upscaling          |
| `qvac-sdk-android-robotics-0.23.0-maven.zip`  | `io.tether:qvac-sdk-android-robotics`  | VLA inference                                                 |

For the experimental desktop JVM client, see the
[package README](https://github.com/tetherto/qvac/blob/main/packages/sdk-kotlin/README.md).

<Steps>
  <Step>
    Download the archive of the profile your app needs, and the release's
    checksums, from the
    [`sdk-v0.23.0` release](https://github.com/tetherto/qvac/releases/tag/sdk-v0.23.0).
    For example, the LLM profile:

    ```bash
    curl -fLO https://github.com/tetherto/qvac/releases/download/sdk-v0.23.0/qvac-sdk-android-llm-0.23.0-maven.zip
    curl -fLO https://github.com/tetherto/qvac/releases/download/sdk-v0.23.0/SHA256SUMS
    sha256sum --check --ignore-missing SHA256SUMS
    # macOS: shasum -a 256 --check --ignore-missing SHA256SUMS
    ```
  </Step>

  <Step>
    Extract it. The archive is a Maven repository: it holds the runtime and the
    Kotlin client modules it depends on.

    ```bash
    unzip qvac-sdk-android-llm-0.23.0-maven.zip -d qvac-maven-repository
    ```
  </Step>

  <Step>
    Declare the extracted directory as a Maven repository, next to JitPack, which
    serves the `bare-rpc-kotlin` dependency the archive does not include:

    ```kotlin
    // settings.gradle.kts
    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
            exclusiveContent {
                forRepository { maven("https://jitpack.io") } // bare-rpc-kotlin
                filter { includeGroupByRegex("com\\.github\\.holepunchto\\..*") }
            }
            maven { url = uri("/absolute/path/to/qvac-maven-repository") }
        }
    }
    ```
  </Step>

  <Step>
    Add the dependency of the archive you downloaded:

    ```kotlin
    // app/build.gradle.kts
    dependencies {
        implementation("io.tether:qvac-sdk-android-llm:0.23.0")
    }
    ```
  </Step>
</Steps>

A project on AGP 9 compiles with the Kotlin Gradle plugin on its classpath, and
bundles Kotlin 2.2 when none is declared. Add the plugin without applying it:

```kotlin
// app/build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android") version "2.4.20" apply false
}
```

All artifacts use the `io.tether.qvac.sdk` namespace, so the Kotlin API is the
same whichever profile you choose. Each profile embeds a capability manifest
that is checked before each request; calling a capability the profile does not
contain throws `UnsupportedCapabilityException`. Inspect it with
`client.runtimeProfile`.

To build the artifacts yourself instead, see the
[package README](https://github.com/tetherto/qvac/blob/main/packages/sdk-kotlin/README.md).

## Connect

```kotlin
val transport = AndroidServiceTransport.connect(applicationContext)
val client = QvacClient(transport)

try {
    println(client.heartbeat())
} finally {
    client.close()
}
```

The service runs in the app's private `:qvac_worker` process. Desktop JVM uses
`JvmBareRpcTransport.connectResolved()`, which starts the resolved `@qvac/sdk`
worker over a loopback channel. Kotlin/Native targets are not published until
they have tested worker hosts.

## Completion and multimodal input

```kotlin
val run = client.completion.run(
    modelId = modelId,
    history = listOf(
        QvacMessage.system("Be concise."),
        QvacMessage.user(
            "What is in this image?",
            attachments = listOf(QvacAttachment("/data/user/0/app/files/photo.jpg")),
        ),
    ),
)

run.tokens.collect(::print)
val answer = run.final.await()
println("${answer.stopReason}: ${answer.stats?.tokensPerSecond} tok/s")
```

Generation defaults to `predict = -1`, so chat does not stop at an arbitrary
token count. A run exposes `events`, `tokens`, `toolCallEvents`, accumulated
text, thinking text, tool calls, statistics, stop reason, and `cancel()`.

## Tool handlers

```kotlin
val weather = QvacTool(
    name = "weather",
    description = "Read local weather",
    parameters = mapOf(
        "city" to QvacToolParameter(QvacToolParameterType.STRING),
    ),
    required = setOf("city"),
    handler = { args -> JsonPrimitive("Sunny in ${args.getValue("city")}") },
)

val run = client.completion.orchestrate(
    modelId = modelId,
    history = listOf(QvacMessage.user("Do I need an umbrella in Rome?")),
    tools = listOf(weather),
)
println(run.text())
```

## Capability examples

```kotlin
val transcript = client.speech.transcribe(
    modelId = whisperModelId,
    audio = QvacDataInput.FilePath("/data/user/0/app/files/sample.wav"),
)

val speech = client.speech.synthesize(ttsModelId, "Hello from QVAC")
val page = client.vision.ocr(ocrModelId, QvacDataInput.FilePath("/data/user/0/app/files/page.png"))
val vector = client.embeddings.embed(embeddingModelId, "private local AI")
val translation = client.translation.run(nmtModelId, "hello", modelType = "nmt")

val imageRun = client.media.diffusion(diffusionModelId, "a small red robot")
val imageBase64 = imageRun.final.await().data.first()
val classes = client.vision.classify(classifierModelId, imageBase64)

val hparams = client.vla.hparams(vlaModelId).hparams
val actions = client.vla.run(
    modelId = vlaModelId,
    images = listOf(frontCamera, wristCamera),
    imgWidth = hparams.visionImageSize,
    imgHeight = hparams.visionImageSize,
    state = qvacVlaPadState(robotState, hparams.maxStateDim),
    tokens = instructionTokens,
    mask = instructionMask,
).actions
```

`client.media` also wraps audio generation, video/world generation, and
upscaling. `client.bci` handles buffered and duplex neural transcription;
`client.vla` handles VLA hparams, inference, and embodiment switching.
The Android Feature Lab sample exercises completion, multimodal vision, OCR,
transcription, TTS, embeddings, translation, and classification with small
models suitable for a Pixel 8a.

## API surface

| Kotlin entry point       | Purpose                                                         |
| ------------------------ | --------------------------------------------------------------- |
| `client.models`          | registry, download, load, unload, and progress                  |
| `client.completion`      | completion runs and tool orchestration                          |
| `client.speech`          | transcription and TTS results                                   |
| `client.vision`          | OCR and classification results                                  |
| `client.translation`     | streaming translation run and cancellation                      |
| `client.embeddings`      | single and batch embeddings                                     |
| `client.media`           | audio, diffusion, video, and upscale runs                       |
| `client.bci`             | buffered and duplex BCI transcription                           |
| `client.vla`             | VLA preprocessing, hparams, inference, and embodiment switching |
| generated client methods | typed operation for every contract method                       |
| `client.raw`             | JSON call, stream, and duplex escape hatch                      |

Generated request and response types live in `io.tether.qvac.sdk.generated.schema`;
`io.tether.qvac.sdk.generated` contains the model constants, error codes, and call
shapes in the shared contract.
