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
40 changes: 40 additions & 0 deletions ai-assistant/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,51 @@ The build resolves `plugin-api.jar` from the repo-root `../libs/`.
3. Install via CodeOnTheGo's Plugin Manager, then restart the IDE.
4. Open **AI Settings** to pick a local model or configure a Gemini API key.

## Gemini key setup (ADFA-2709)

The Gemini pane guides key acquisition instead of just showing an empty field:

- **Get a free key** opens `https://aistudio.google.com/apikey` in the *system*
browser. AI Studio provisions the underlying Cloud project itself, so the
Google Cloud console is not part of the flow, and sign-in happens in the
browser — this process never sees a Google password.
If no browser can handle the intent, the URL is copied to the clipboard instead
so there is still a way forward.
- **The clipboard is never read.** Pasting the key is left to the field's own
long-press menu, which keeps Android 13+'s system read notice tied to a
deliberate user action instead of firing on a background probe. Returning from
AI Studio only shows a hint pointing at the field (or at **Edit**, when a key is
already stored).
- **Save checks the key with Google before storing it.** A key Google rejects
(HTTP 400/401/403) is **not** persisted. A key that can't be checked — offline,
or `ai-core` unavailable — prompts a save-anyway confirmation and is recorded as
unverified, so the status line doesn't claim more than was established. HTTP 429
counts as valid: a rate-limited key is a working key.

The check reuses `GeminiBackend.listModels(apiKey)` in `ai-core` (which already
holds `network.access`), so this plugin's manifest gains no new permission and no
new dependency. `gemini/` holds the pieces: `GeminiCatalogGateway` (the one
reflective seam into `ai-core`), `CatalogResult` (what one lookup returned),
`KeyVerification` (the verdict + classifier), and `GeminiKeyOnboarding` (the AI
Studio URL).

That reflective seam means the two plugins ship as a pair: the `listModels(apiKey)`
overload is new, and against an older `ai-core` the lookup fails with
`NoSuchMethodException`, which lands in the same save-anyway prompt as being
offline. It deliberately does **not** fall back to the no-arg `listModels()` —
that call authenticates with the *saved* key, so it would clear a candidate key on
the strength of a different credential.

**No shape check on the key.** AI Studio issues authorization-type keys that don't
match the classic `AIza…` form; Save gates on blankness alone and lets the live
check decide.

## Key classes

- `AiAssistantPlugin.kt` — plugin entry point / lifecycle
- `fragments/ChatFragment.kt`, `viewmodel/ChatViewModel.kt` — chat UI + state
- `fragments/AiSettingsFragment.kt`, `viewmodel/AiSettingsViewModel.kt` — model/backend config
- `gemini/` — Gemini key onboarding + pre-save verification
- `tool/` — the agent tool-loop (executor, router, per-tool handlers, approval)

## Security
Expand Down
8 changes: 7 additions & 1 deletion ai-assistant/ai-assistant.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,13 @@ <h3>Install &amp; configure</h3>
<li>Copy both <code>.cgp</code> files to the device and install via the CoGo
Plugin Manager — <b>AI Core first</b>, then AI Assistant. Restart the IDE.</li>
<li>Open <b>Settings</b> and either select a <code>.gguf</code> model (Local)
or enter a Gemini API key (Gemini).</li>
or set up a Gemini API key (Gemini).</li>
<li>For Gemini, tap <b>Get a free key</b> to open Google AI Studio in your
browser — it creates the underlying Cloud project for you, so no Google
Cloud console visit is needed. Copy the key, paste it into the key field,
then tap <b>Save Key</b>. Saving checks the key with Google first: a key Google rejects
is not stored, and one that can't be checked (offline, or AI Core disabled)
is kept only if you confirm.</li>
<li>Open the <b>Agent</b> tab and start chatting.</li>
</ul>

Expand Down
57 changes: 53 additions & 4 deletions ai-assistant/src/main/assets/docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ <h2>Choosing a backend</h2>
<li><b>Local (on-device)</b> — runs a <code>.gguf</code> model via
llama.cpp. Open <b>Settings</b>, pick a model file from your Downloads
folder. Nothing leaves the device.</li>
<li><b>Gemini (cloud)</b> — enter a Gemini API key in <b>Settings</b>.
Prompts and any file contents the agent reads are sent to Google over
HTTPS.</li>
<li><b>Gemini (cloud)</b> — enter a Gemini API key in <b>Settings</b>; see
<i>Getting a free Gemini key</i> below. Prompts and any file contents the
agent reads are sent to Google over HTTPS.</li>
</ul>

<h2>What the agent can do</h2>
Expand Down Expand Up @@ -75,6 +75,51 @@ <h2>Attaching context files</h2>
<li>On the Gemini backend, attached file contents leave the device.</li>
</ul>

<h2>Getting a free Gemini key</h2>
<p>You do <b>not</b> need the Google Cloud console. Keys are created at
<b>aistudio.google.com/apikey</b>, and Google AI Studio sets up the underlying
Cloud project for you the first time you accept its terms.</p>
<ol>
<li>In <b>Settings</b> with the <b>Gemini</b> backend selected, tap
<b>Get a free key</b>. Your normal browser opens at AI Studio.</li>
<li>Sign in with your Google account <i>in the browser</i> and tap
<b>Create API key</b>. This plugin never sees your Google password.</li>
<li>Copy the key Google shows you, return to the IDE and paste it into the
<b>Gemini API Key</b> field (long-press the field, then <i>Paste</i>).</li>
<li>Tap <b>Save Key</b>. The key is checked with Google immediately.</li>
</ol>
<p>What <b>Save</b> reports:</p>
<ul>
<li><b>Verified — your API key works</b> — Google accepted the key and it has
been stored.</li>
<li><b>Key accepted, Google is rate-limiting</b> — the key is valid and stored;
the quota is busy right now, not the credential.</li>
<li><b>Invalid API key</b> — Google refused it, so <b>nothing is saved</b>.
Check what you pasted and try again. Keys are long, single-line, and contain
no spaces.</li>
<li><b>Couldn't reach Google</b> / <b>Couldn't check this key</b> — the check
itself failed (no network, or the AI Core plugin is disabled or out of
date), so you are
asked whether to keep the key anyway. Kept this way it is stored but not
confirmed, and the status line says so.</li>
</ul>
<p>If no browser is installed, the AI Studio link is copied to the clipboard so
you can open it on another device and type the key in by hand.</p>

<div class="note">
AI Studio isn't available in every country. If you can't create a key, the
<b>Local</b> backend needs no account at all — pick a <code>.gguf</code> chat
model and everything runs on the device.
</div>

<h2>Free tier and your data</h2>
<p>Gemini has a free tier that is enough to use this plugin. Be aware that on
the <b>free</b> tier Google may use prompts and responses to improve its
products; the paid tier does not. Because the agent sends your prompts, project
context and the contents of files it reads, that applies to your code too. This
has always been true of the Gemini backend — it is written down here so it isn't
a surprise. The <b>Local</b> backend sends nothing anywhere.</p>

<h2>Your Gemini API key</h2>
<p>The key is encrypted with AES/GCM under a hardware-backed Android Keystore
secret, and only the ciphertext is written to the plugin's private storage —
Expand All @@ -100,7 +145,11 @@ <h2>Troubleshooting</h2>
<ul>
<li><b>"No model configured"</b> — select a <code>.gguf</code> file (Local)
or set an API key (Gemini) in Settings.</li>
<li><b>Gemini errors</b> — check the API key, network connection, and quota.</li>
<li><b>Gemini errors</b> — check the API key, network connection, and quota.
Re-tapping <b>Save</b> on the key re-runs the live check and tells you which
of the three it is.</li>
<li><b>"Invalid API key"</b> — the key was not stored. Make sure you copied
the key itself from AI Studio and not the page's URL.</li>
<li><b>Agent tab missing</b> — confirm both AI Core and AI Assistant are
installed and the IDE was restarted.</li>
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class AiAssistantPlugin : IPlugin, UIExtension, DocumentationExtension {
const val TOOLTIP_TAG_SETTINGS_SIMPLE_PROMPT = "ai_settings_simple_prompt"
const val TOOLTIP_TAG_SETTINGS_GEMINI_KEY = "ai_settings_gemini_key"
const val TOOLTIP_TAG_SETTINGS_GEMINI_MODEL = "ai_settings_gemini_model"
const val TOOLTIP_TAG_SETTINGS_GET_KEY = "ai_settings_get_free_key"

@Volatile
private var pluginContext: PluginContext? = null
Expand Down Expand Up @@ -366,21 +367,54 @@ class AiAssistantPlugin : IPlugin, UIExtension, DocumentationExtension {
tag = TOOLTIP_TAG_SETTINGS_GEMINI_KEY,
summary = "Enter your Google Gemini API key. It is stored only on this device.",
detail = """
<p>Paste a Gemini API key to enable the cloud backend. The key is
encrypted with a key held in this device's hardware-backed Android
Keystore before it is written to this plugin's private preferences,
and is sent only to Google's API over HTTPS. Requests (your prompts
and project context) leave the device when Gemini is selected.</p>
<p>Use the eye button to check what you typed, <b>Save</b> to store
it, <b>Edit</b> to change it later and <b>Clear</b> to remove it
from the device.</p>
<p>Paste a Gemini API key to enable the cloud backend. Keys are free
to create at <b>aistudio.google.com/apikey</b> — tap <b>Get a free
key</b> to go straight there. Google AI Studio sets up the
underlying Cloud project for you, so there is no Cloud console and
no billing setup involved.</p>
<p>The key is encrypted with a key held in this device's
hardware-backed Android Keystore before it is written to this
plugin's private preferences, and is sent only to Google's API over
HTTPS. Requests (your prompts and project context) leave the device
when Gemini is selected.</p>
<p><b>Save</b> checks the key with Google before storing it, so a
key that doesn't work is reported straight away instead of failing
later mid-chat — a key Google rejects is not saved at all. If the
check can't be completed (no network, or the AI Core plugin is
disabled or out of date) you are asked whether to keep the key
anyway.</p>
<p>Use the eye button to check what you typed, <b>Edit</b> to change
the key later and <b>Clear</b> to remove it from the device.</p>
<p>If the Keystore entry is ever lost — clearing the app's data,
for instance — the stored key can no longer be decrypted and must
be re-entered here.</p>
""".trimIndent(),
buttons = listOf(
PluginTooltipButton(description = "AI Assistant guide", uri = "index.html", order = 0)
)
),
PluginTooltipEntry(
tag = TOOLTIP_TAG_SETTINGS_GET_KEY,
summary = "Open Google AI Studio in your browser to create a free Gemini API key.",
detail = """
<p>Opens <b>aistudio.google.com/apikey</b> in your normal browser,
where you sign in with your Google account and tap <i>Create API
key</i>. AI Studio creates the Cloud project behind the scenes — the
Google Cloud console is not part of this.</p>
<p>Sign-in happens in the browser, so this plugin never sees your
Google password. Copy the key Google shows you, come back here and
paste it into the key field, then tap <b>Save Key</b>.</p>
<p>Gemini has a free tier. Note that on the free tier Google may use
prompts and responses to improve its products — and this plugin
sends your prompts and any file contents the agent reads. If that
matters for your project, use the on-device <b>Local</b> backend
instead: nothing leaves the device.</p>
<p>If no browser is installed the link is copied to the clipboard
so you can open it elsewhere.</p>
""".trimIndent(),
buttons = listOf(
PluginTooltipButton(description = "AI Assistant guide", uri = "index.html", order = 0)
)
)
)

Expand Down
Loading