git.iko.soy/opencode-android
on aed5cef help sign in
git clone https://git.iko.soy/opencode-android.git

opencode-android

An Android client for the opencode server, built with Crux (shared Rust core + Kotlin shell) and Jetpack Compose.

Architecture

┌─────────────────────────────────────────────────┐
│  Shell (Kotlin / Jetpack Compose)               │
│  ┌─────────────┐  ┌──────────────────────────┐  │
│  │ Compose UI   │  │  HttpClient / SseClient  │  │
│  │ (3 screens)  │  │  (Ktor + OkHttp)         │  │
│  └──────┬───────┘  └────────────┬─────────────┘  │
│         │                       │                │
│    Event (bincode)        Effect (bincode)        │
│         │                       ▲                │
│  ┌──────▼───────────────────────┴────────────┐   │
│  │  Core (ViewModel) — FFI bridge            │   │
│  │  CoreFfi → NativeCoreFfi / PureCoreFfi    │   │
│  └───────────────────────────────────────────┘   │
└──────────┬──────────────────────────────────────┘
           │  JNI (bincode bytes)
┌──────────▼──────────────────────────────────────┐
│  Core (Rust / Crux)                              │
│  ┌────────┐  ┌─────────┐  ┌──────────┐          │
│  │  App   │  │  Model  │  │ Commands │          │
│  │ update │  │ state   │  │ effects  │          │
│  └────────┘  └─────────┘  └──────────┘          │
│  No side-effects — pure logic                   │
└─────────────────────────────────────────────────┘

Rust Core (shared/)

Kotlin Shell (app/)

Generated Types (generated/)

Building

Prerequisites

Generate Kotlin types

cd shared
cargo run --bin codegen --features codegen -- --language kotlin --output-dir ../generated

Build native library (requires NDK)

# Install Android targets
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android

# Build and pack with BoltFFI
boltffi pack android

Build the APK

./gradlew assembleDebug

The app works without the native library — PureCoreFfi provides a pure-Kotlin reimplementation of the core logic. When libopencode.so is present, NativeCoreFfi is used automatically.

Run tests

# Rust core tests
cd shared && cargo test

# Kotlin shell tests
./gradlew testDebugUnitTest

Basic Auth Detection

The opencode server uses HTTP Basic Auth when OPENCODE_SERVER_PASSWORD is set (username defaults to opencode). The client detects this automatically:

  1. On Connect, the core probes GET /global/health without credentials
  2. If the server responds 401, the core sets auth_required = true in the ViewModel
  3. The UI shows username/password fields
  4. The user enters credentials and taps Connect again
  5. The core retries the probe with Authorization: Basic <base64(user:pass)>
  6. On 200, the core marks authed = true and all subsequent requests (sessions, messages, send, SSE) carry the Authorization header
  7. On 401 with credentials, the core shows "Invalid credentials"

Crash Logs

CrashLogger installs as the global Thread.UncaughtExceptionHandler in OpencodeApp.onCreate(). When a crash occurs:

The crash log count is displayed on the Connect screen.

API Coverage

The client connects to an opencode server and supports:

Configuration

The default server URL is http://localhost:4096. Change it in the Connect screen. android:usesCleartextTraffic="true" is enabled for local development.