git.iko.soy/opencode-android
on v2026.07.08.2112 help sign in
opencode-android/README.md
153 lines 7.1 kB history raw
1# opencode-android
3A native Android client for the [**opencode**](https://opencode.ai) server (the
4`sst/opencode` AI coding agent), built with **Crux** (a Rust core), **Kotlin**,
5and **Jetpack Compose**.
7Connect to a running `opencode serve`, browse and create sessions, and chat with
8the agent — assistant replies (text, reasoning, and tool calls) stream in live
9over the server's SSE event stream.
11<p align="center"><em>Connect → Sessions → Chat, with token-by-token streaming.</em></p>
13## Architecture
15The app follows the Crux pattern: **all application logic lives in a pure,
16platform-agnostic core; the platform is a thin shell** that renders the core's
17view model, sends it events, and performs the side effects it requests.
19```
20┌─────────────────────────── shared/ (Rust) ───────────────────────────┐
21│ Crux core — the single source of truth │
22│ • Model / Event / Effect / ViewModel │
23│ • update(): connect+auth, sessions, chat, SSE stream application │
24│ • crux_http for HTTP effects; unit-tested with `cargo test` │
25└───────────────────────────────────────────────────────────────────────┘
26 ▲ mirrored, behaviour-for-behaviour
27┌─────────────────────────── app/ (Kotlin) ────────────────────────────┐
28│ Jetpack Compose shell │
29│ • OpencodeCore — Kotlin port of the reducer (same ViewModel) │
30│ • HttpClient (OkHttp) + SseClient (okhttp-sse) — the effects │
31│ • Compose UI: Connect / Sessions / Chat │
32└───────────────────────────────────────────────────────────────────────┘
33```
35### Why a Kotlin port instead of a JNI `.so`?
37Crux normally compiles the Rust core to a native library and the shell talks to
38it over an FFI bridge. That requires an Android cross-compilation toolchain
39(NDK + a Rust `std` for the `*-linux-android` targets). This project's dev
40environment intentionally ships **without** an NDK, so instead:
42- The **Rust core is the canonical specification** of behaviour and is fully
43 unit-tested (`cd shared && cargo test`).
44- The **Android shell runs `OpencodeCore`**, a faithful Kotlin port of the same
45 reducer, driving an identical `UiState`/`ViewModel`.
47The FFI boundary is kept clean, so the day an NDK is available the Kotlin port
48can be swapped for the compiled `.so` behind the same seam. The crate already
49builds as a `cdylib`/`staticlib` and a `codegen` binary is wired for Crux
50type-generation.
52## Project layout
54```
55flake.nix Reproducible dev shell (JDK 17, Gradle, Rust, Android SDK 35)
56shared/ Crux core (Rust)
57 src/app.rs App: Model, Event, Effect, ViewModel, update, view, tests
58 src/protocol.rs Lenient serde wire types for the opencode HTTP + SSE API
59 src/bin/codegen.rs Type-generation entry point (feature = "typegen")
60generated/ Crux-generated FFI-shared JVM types (from `codegen`)
61app/ Android app (Kotlin + Compose)
62 src/main/java/soy/iko/opencode/
63 core/ OpencodeCore (reducer port), Protocol, HttpClient, SseClient
64 ui/ Compose screens (Connect/Sessions/Chat), theme, components
65 MainActivity.kt, CoreViewModel.kt
66```
68## Getting started
70### Dev shell
72Everything you need is provided by the Nix flake:
74```sh
75nix develop # or: direnv allow (an .envrc with `use flake` is included)
76```
78This puts JDK 17, Gradle, `rustc`/`cargo`, and the Android SDK (platform 35,
79build-tools 35.0.0) on your `PATH`.
81### Build & test
83```sh
84# Rust core — logic + streaming unit tests
85cd shared && cargo test
87# Android app — debug APK
88./gradlew assembleDebug # -> app/build/outputs/apk/debug/app-debug.apk
90# Android unit tests + lint
91./gradlew testDebugUnitTest lintDebug
92```
94### Run against a server
96Start opencode's server on your machine or LAN:
98```sh
99opencode serve --port 4096 # optionally behind OPENCODE_SERVER_PASSWORD
100```
102Install the APK, launch the app, and enter the server URL (e.g.
103`http://192.168.1.10:4096`). If the server was started with a password the app
104detects the `401`, shows credential fields, and connects with HTTP Basic auth.
106> Cleartext HTTP is enabled so you can reach a `http://` server on your LAN.
108## The opencode API this client speaks
110- **Connect / auth** — `GET /global/health` probes reachability; a `401` triggers
111 HTTP Basic auth.
112- **Sessions** — `GET /session`, `POST /session`, `DELETE /session/{id}`.
113- **Messages** — `GET /session/{id}/message` (each item is `{ info, parts }`).
114- **Send** — `POST /session/{id}/prompt_async` (returns immediately; the reply
115 streams over SSE).
116- **Cancel** — `POST /session/{id}/abort`.
117- **Stream** — `GET /event` (SSE). The client applies `message.updated`,
118 `message.part.updated` (text/reasoning/tool, with deltas), `message.removed`,
119 `session.idle` (clears the "generating" state), `session.error`, and
120 `session.{created,updated,deleted}` incrementally. Unknown event types are
121 ignored so new server versions don't break the stream.
123## Continuous integration & releases
125- **CI** (`.github/workflows/build.yml`) runs on pushes to `master` and PRs: it
126 unit-tests the Crux core (`cargo test`), runs the Android unit tests + lint,
127 and builds the debug APK (uploaded as an artifact).
128- **Releases** (`.github/workflows/release.yml`): push a version tag to cut a
129 release — it builds a minified, signed release APK and publishes a GitHub
130 Release with the APK attached and a signed build-provenance attestation.
132 ```sh
133 git tag v1.0.0 && git push origin v1.0.0 # or run the workflow manually
134 ```
136 The tag name becomes the version (a manual run uses a `YYYY.MM.DD.HHMM`
137 timestamp), stamped into the APK via `-PversionName/-PversionCode`.
139 **Signing:** set the repo secrets `OPENCODE_STORE_BASE64` (base64 of your
140 keystore), `OPENCODE_STORE_PASSWORD`, `OPENCODE_KEY_ALIAS`, and
141 `OPENCODE_KEY_PASSWORD` to sign with your own key. Without them the release
142 build falls back to the debug key, so the APK is still installable.
144## Notes & limitations
146- Rendering covers text, reasoning, and tool-call parts. Interactive tool
147 **permission** prompts (`permission.updated`) are not yet surfaced.
148- Model/agent selection uses the server's configured default (no picker yet).
149- No `.so` is built in this environment (see *Architecture* above).
151## License
153Apache-2.0.