Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions ai-agent-local/ai-agent-local.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,16 @@ <h2>Core functionality</h2>
<li><b>Model safety checks</b> — inspects a selected <code>.gguf</code>
header and refuses embedding-only models for chat, with a clear error
instead of a native crash.</li>
<li><b>Actionable load failures</b> — a failed load is classified (missing,
empty, not a GGUF, out of memory, unsupported quantization) and reported as
a message that says what to do next.</li>
<li><b>Storage-picker support</b> — a model chosen as a
<code>content://</code> document is copied once into private storage so the
native loader can open it, and only the current model is kept.</li>
<li><b>Actionable load failures</b> — a failed load is classified (no longer
reachable, streamed rather than local, empty, not a GGUF, out of memory,
unsupported quantization) and
reported as a message that says what to do next.</li>
<li><b>Direct storage access</b> — a model chosen as a <code>content://</code>
document is read in place, through the read grant the picker persisted.
Nothing is copied into private storage, so a multi-gigabyte model costs no
device space beyond the file you downloaded.</li>
<li><b>Its own settings pane</b> — browse for a <code>.gguf</code> file,
re-load a previously imported model, record the model's published SHA-256,
re-load the model already selected, record the model's published SHA-256,
and choose between the short system prompt small models follow
reliably and the full tool-calling one. A model too large for the device's
free RAM raises a warning first.</li>
Expand All @@ -82,14 +84,15 @@ <h2>Technical architecture</h2>
<tr><td><code>LocalLlmPlugin</code></td><td>Plugin entry point. Registers the
backend with AI Core on activation, re-registering if AI Core activates
later; frees the native model on dispose.</td></tr>
<tr><td><code>LocalLlmBackend</code></td><td>The inference engine. Resolves
the selected model to a real file path, manages loading and unloading, and
serializes generations against the shared native context.</td></tr>
<tr><td><code>LocalLlmBackend</code></td><td>The inference engine. Opens the
selected model in place and hands the native loader that descriptor, manages
loading and unloading, and serializes generations against the shared native
context.</td></tr>
<tr><td><code>GgufModelInspector</code></td><td>Minimal GGUF header reader
that classifies a model as chat- or embedding-only.</td></tr>
<tr><td><code>ModelLoadDiagnostics</code></td><td>Classifies a load failure
from the file, free memory and the native error text, as a pure function
that is unit-tested off-device.</td></tr>
from the model's size and readability, free memory and the native error
text, as a pure function that is unit-tested off-device.</td></tr>
<tr><td><code>ModelLoadMessages</code></td><td>Renders a diagnosis as
user-facing text, keeping string resources out of the engine.</td></tr>
<tr><td><code>LocalLlmSettingsFragment</code></td><td>The settings pane AI
Expand All @@ -107,11 +110,14 @@ <h2>Usage</h2>
Manager, then restart the IDE.</li>
<li>Open <b>Preferences &rarr; Configuration &rarr; Agent</b> and select the
<b>local</b> backend. This plugin's own pane appears below it.</li>
<li>Tap <b>Browse</b> and pick a <code>.gguf</code> model file. The file is
copied once into private storage, then loaded; a model larger than the free
RAM asks you to confirm first.</li>
<li>Tap <b>Browse</b> and pick a <code>.gguf</code> model file. It is loaded
from wherever you saved it, with no copy made; a model larger than the free
RAM asks you to confirm first. Leave the file in place — moving or deleting
it breaks the selection. The picker offers device-local documents only: a
model still in a cloud folder can only be read as a stream, which the
in-place loader cannot use.</li>
<li>Optionally record the model's published <b>SHA-256</b>, or use <b>Load
from saved</b> to return to a model you already imported.</li>
from saved</b> to return to the model you already selected.</li>
</ol>
<div class="note">
Model choice drives whether this works at all on a given device. A Q4_K_M
Expand Down
3 changes: 3 additions & 0 deletions ai-agent-local/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ dependencies {
testImplementation(files("../libs/plugin-api.jar"))
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.13.8")
// LiveData's postValue needs the arch-core executor swapped for a synchronous one; the
// settings pane publishes its state through it, so its tests cannot run without this.
testImplementation("androidx.arch.core:core-testing:2.2.0")
}

// The one ABI this plugin ships. Shared by the packaging check and the unit tests.
Expand Down
6 changes: 4 additions & 2 deletions ai-agent-local/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@
android:name="plugin.max_ide_version"
android:value="26.99" />

<!-- filesystem.*: reads the selected .gguf and caches a copy under the plugin's
files dir. native.code: bundles the prebuilt llama.cpp .so libraries. -->
<!-- filesystem.read: reads the selected .gguf in place, through the read grant the
document picker persisted — no copy is made. filesystem.write: deletes the model
copies earlier releases left behind (LocalLlmBackend.deleteLegacyModelCache).
native.code: bundles the prebuilt llama.cpp .so libraries. -->
<meta-data
android:name="plugin.permissions"
android:value="filesystem.read,filesystem.write,native.code" />
Expand Down
27 changes: 20 additions & 7 deletions ai-agent-local/src/main/assets/docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,16 @@ <h2>The settings pane</h2>
controls:</p>
<ul>
<li><b>Browse</b> — opens the system file picker to choose a
<code>.gguf</code> model. A model selected as a <code>content://</code>
document is copied once into the plugin's private storage so the native
loader can open it, and only the current model is kept on disk. If the file
is larger than the device's free RAM, a warning asks you to confirm before
loading.</li>
<li><b>Load from saved</b> — reloads the model already in private storage
without picking it again. Use this after restarting the IDE, or when a load
<code>.gguf</code> model. The plugin keeps read access to the document you
picked and reads it where it is — on internal storage, an SD card or a USB
volume. Nothing is copied, so a multi-gigabyte model costs no extra device
storage. Keep the file where it is: moving or deleting it breaks the
selection. The picker offers only documents already stored on the device,
because a model still in a cloud folder has to be read as a stream and
cannot be loaded in place. If the file is larger than the device's free RAM,
a warning asks you to confirm before loading.</li>
<li><b>Load from saved</b> — reloads the model you already selected without
picking it again. Use this after restarting the IDE, or when a load
failed for a transient reason such as low memory.</li>
<li><b>SHA-256</b> — optional. Paste the checksum published alongside the
model download to keep a record of which exact file is configured. It is
Expand Down Expand Up @@ -93,6 +96,16 @@ <h2>Troubleshooting</h2>
the safest starting point).</li>
<li><b>The <code>local</code> backend never appears</b> — AI Core isn't
installed or activated; install it and restart the IDE.</li>
<li><b>"The selected model can no longer be reached"</b> — the model is read
where you saved it rather than from a copy, so moving, renaming or deleting
the file, or removing the SD card it lives on, breaks the selection.
Clearing the IDE's app data also withdraws the permission to read it. Pick
the model again with <b>Browse</b>.</li>
<li><b>"This model is streamed from its storage location"</b> &mdash; the file
you picked lives in a cloud folder (Google Drive, OneDrive) rather than on
the device, and can only be read as a stream. Download the
<code>.gguf</code> to the device &mdash; Downloads is fine &mdash; and pick
it from there.</li>
</ul>
</body>
</html>
Loading
Loading