An in-IDE AI chat assistant with tool calling. It provides the chat UI, settings,
and the agent tool-loop (read/write files, list/search the project, add
dependencies, run Gradle sync, etc.), and delegates all model inference to the
sibling ai-core plugin.
This is the frontend plugin. It has no compile-time dependency on
ai-core; the two communicate at runtime through the CodeOnTheGo plugin manager (SharedServices). Installai-corefirst, thenai-assistant.
┌──────────────────────────┐
│ ai-assistant (this) │ ← Chat UI, settings, agent tool-loop
└────────────┬─────────────┘
│ SharedServices (runtime)
▼
┌──────────────────────────┐
│ ai-core │ ← LLM inference: local (llama.cpp) + Gemini
└──────────────────────────┘
- Chat UI (RecyclerView + Markdown rendering via Markwon)
- Agent tool-loop with per-tool approval dialogs
- Session persistence
- Works with either backend exposed by
ai-core(on-device GGUF or Gemini API)
Prerequisites: Android SDK (API 33+), JDK 17. Create local.properties with
sdk.dir=.... This plugin needs no NDK, submodule, or native toolchain.
cd ai-assistant
./gradlew assemblePlugin # release -> build/plugin/ai-assistant.cgp
./gradlew assemblePluginDebug # debug variantThe build resolves plugin-api.jar from the repo-root ../libs/.
- Build and install
ai-corefirst (see../ai-core/README.md). - Build this plugin, copy
build/plugin/ai-assistant.cgpto the device. - Install via CodeOnTheGo's Plugin Manager, then restart the IDE.
- Open AI Settings to pick a local model or configure a Gemini API key.
AiAssistantPlugin.kt— plugin entry point / lifecyclefragments/ChatFragment.kt,viewmodel/ChatViewModel.kt— chat UI + statefragments/AiSettingsFragment.kt,viewmodel/AiSettingsViewModel.kt— model/backend configtool/— the agent tool-loop (executor, router, per-tool handlers, approval)
- Gemini API key at rest. The key is encrypted with AES/GCM under a
hardware-backed Android Keystore secret and only the ciphertext is written to
this plugin's private
SharedPreferences(AgentSettings), asenc:v1:+ base64(iv‖ciphertext). A copied prefs file — root,adb backup, forensic dump — is useless without this device's Keystore. Remove the key any time from AI Settings (orclearGeminiApiKey()).- A key stored before this plugin encrypted them is still plaintext on disk;
it is re-encrypted in place the first time it's read
(
SecureApiKeyStore.readAndMigrate), so no user action is needed. - The key is not bound to user authentication
(
setUserAuthenticationRequiredis not set), so it stays usable for background inference and a lock-screen credential change does not invalidate it. If the Keystore entry is lost anyway — the app's data is cleared, or an OEM Keystore drops the alias — the stored key can no longer be decrypted and must be re-entered; Edit says so instead of presenting an empty field. Saving surfaces a failure message rather than silently storing nothing. - Encryption at rest defends against offline recovery of the prefs file. It
does not defend against code already running as the host IDE's UID, which can
simply call
decrypt— that would need a host-provided secure-storage service with per-plugin isolation.
- A key stored before this plugin encrypted them is still plaintext on disk;
it is re-encrypted in place the first time it's read
(
- Shared crypto across two plugins.
ai-coreperforms the Gemini calls and therefore has to read the same key. Both plugins run in the host IDE's process (same UID) and so share one Android Keystore; each ships an identical copy ofSecureApiKeyStorepinned to the aliascotg_ai_gemini_key_v1. The two copies must stay byte-identical apart from their package line — ifALIAS,TRANSFORM,IV_LENorENC_PREFIXdrift,ai-coresilently fails to decrypt and Gemini reports "backend not available". A host-provided secure-storage service would remove the duplication. - Gemini API key in transit. The key is sent to Google as an
x-goog-api-keyrequest header (and via the SDK on the chat path), never in a URL query string. - File tools are confined to the project root — see the in-IDE help page.
- The context-file picker is confined to the open project and fails closed: with no project open it refuses to open rather than falling back to a broader root.
GPL-3.0 — same as AndroidIDE / CodeOnTheGo.