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
50 changes: 47 additions & 3 deletions plugins/AI-Agent-Claude/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ cd plugins/AI-Agent-Claude

## Configuration

Everything is configured in **AI Core → Agent settings**, on the pane this plugin
contributes: API key, model, and one **Test Connection & List Models** button.
Everything is configured in **Preferences → Configuration → Agent**, on the pane
this plugin contributes: API key, model, and one **Test Connection & List Models** button.
Nothing outside this plugin handles the key. Listing models and testing the key
are the same `GET /v1/models`, so they are one control, and the model is a
single editable dropdown: type any id, or pick one the key can use.
Expand Down Expand Up @@ -121,6 +121,48 @@ register with. Order does not matter: this plugin re-registers when it sees
ai-core activate. Copy `build/plugin/ai-agent-claude.cgp` to the device, install
via CodeOnTheGo's Plugin Manager, then restart the IDE.

## System prompt config

The prompt Claude asks ai-core to send lives in `src/main/assets/prompts/`, one YAML
file per concern, apart from the code that sends it. Changing the tone, adding a
rule or translating the prompt is an edit to those files alone. ai-core appends its
own IDE CONTEXT block after the rendered prompt.

The files are loaded, validated and cached once, when the plugin is activated.
`getSystemPrompt` renders `layout.yml` from that cache for each request, since the
tool list, the protocol and the example path vary per run; it never waits. Until the
config has loaded, or if it cannot render, it returns null and ai-core sends its own
default prompt.

| File | Keys | What it is |
|---|---|---|
| `agent.yml` | `schema_version`, `identity`, `include` | The entry point: the version (`1`; another is refused rather than misread), who the agent is, and the files below. |
| `scope.yml` | `scope` | What the agent will answer: anything, with the project's tools only when the request is about the open project. |
| `rules.yml` | `rules` | Priority groups, highest first; each has a `heading` (`CRITICAL`, `IMPORTANT`, `MANDATORY`, `OPTIONAL`) and its `items`. **Adding a rule is adding an item.** |
| `workflow.yml` | `behavior`, `workflow` | How to go about building or changing something; the workflow's `steps` are numbered when rendered. |
| `tools.yml` | `tools`, `tool_call_format` | What introduces the tool list, and how to call a tool: `native` under the function-calling API, `text` (with its examples) when calls travel in the reply. Exactly one is sent. |
| `layout.yml` | `layout.system_prompt` | Where each text goes. |

Loading and checking follow ai-core's rules (see ai-core's README): a key belongs to
one file, only `agent.yml` includes, and a missing, unknown, misspelled or duplicate
key, an empty list or an unquoted number is refused naming the file and path, e.g.
`rules.yml: rules[1].items is empty`. Texts are named by their YAML path in upper
case (`scope.heading` is `SCOPE_HEADING`); each rule group has `HEADING` and `ITEMS`,
each item and step has `TEXT`, each step has `NUMBER`, and each example has `PURPOSE`
and `CALL`. The request's values are `TOOLS` (each with `NAME`, `DESCRIPTION`,
inserted verbatim), `TOOL_CALL_SYNTAX` (null under native calling),
`NATIVE_TOOL_CALLS`, `EXAMPLE_FILE_PATH` and `EXAMPLE_FILE_STEM`.

Rendering is strict: an unknown name throws, naming the text it was in. Activation
renders the prompt for requests that open and close every section and logs any
failure, and `ClaudeSystemPromptTest` fails on one in the shipped files. A new key
needs `ClaudePromptConfig` and its parser; a new name needs `ClaudePromptVariables`.

The engine and the YAML plumbing (`PromptTemplateEngine`, `PromptConfigLoader`,
`PromptConfigStore`, `PromptConfigObject`, ...) are the IDE's, in `plugin-api.jar`'s
`com.itsaky.androidide.plugins.ai.prompt`, shared with ai-core and the other backends.
Only `ClaudePromptConfig`, its mapping in `ClaudePromptConfigParser`, and `sharedPromptConfig` are this plugin's own.

## Key classes

- `plugin/ClaudePlugin.kt` — entry point; registers the backend with ai-core
Expand All @@ -134,7 +176,9 @@ via CodeOnTheGo's Plugin Manager, then restart the IDE.
- `backend/ClaudeModelCatalog.kt` — reads `GET /v1/models` (pure)
- `backend/ClaudeHttpClient.kt` — sockets, headers and timeouts
- `errors/ClaudeErrorFormatter.kt` — turns a failure into one translated sentence
- `prompt/ClaudeSystemPrompt.kt` — the system prompt this cloud model is given
- `prompt/ClaudeSystemPrompt.kt` — renders `layout.yml` from `ClaudePromptVariables`;
`prompt/config/` maps `assets/prompts/` onto this plugin's config type, which the
IDE's `ai.prompt` package loads, validates, caches and renders
- `settings/` — the pane this backend contributes to the selector
- `logging/` — `LOG_PREFIX` (`AiAgentClaude`), prefixing every logcat tag

Expand Down
25 changes: 24 additions & 1 deletion plugins/AI-Agent-Claude/ai-agent-claude.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,40 @@ <h2>Technical architecture</h2>
calls, per-model parameters, retries and error classification.</li>
<li>The API key is encrypted with the IDE's <code>KeystoreSecretStore</code>
under this plugin's own alias.</li>
<li>The system prompt lives in <code>assets/prompts/</code>, one YAML file per
concern, so its wording changes without touching code. It is loaded and
checked once on activation; if it cannot load or render, AI Core's default
prompt is sent instead.</li>
<li>Registration with AI Core goes through the IDE's
<code>LlmBackendRegistration</code>, which re-registers when AI Core restarts
and tells the chat when the key or model changes, so its backend tag names
the model in use.</li>
</ul>

<h2>Usage</h2>
<ol>
<li>Install <b>AI Core</b> and this plugin, then restart the IDE.</li>
<li>Open <b>AI Core → Agent settings</b> and select <b>Claude</b>.</li>
<li>Open <b>Preferences &rarr; Configuration &rarr; Agent</b> and select
<b>Claude</b>. This plugin's own pane appears below it.</li>
<li>Tap <b>Get API Key</b>, create a key in the Claude Console, paste it and
tap <b>Save Key</b>.</li>
<li>Optionally pick a model, then start a chat.</li>
</ol>

<h2>Key benefits</h2>
<ul>
<li><b>Frontier models on any device</b> — Claude runs in Anthropic's cloud,
so a phone that could not run a large model locally can still use one.</li>
<li><b>Reliable tool use</b> — the Agent's tools are declared to Claude
natively, so edits arrive as structured calls rather than text that has to
be parsed.</li>
<li><b>Small footprint</b> — no bundled model or native library.</li>
<li><b>Credential hygiene</b> — the key is checked before it is saved,
encrypted at rest, and dropped from memory when the plugin unloads.</li>
<li><b>Coexists with the other backends</b> — install it beside AI Agent Local,
Gemini or OpenAI and switch between them in <b>Preferences &rarr; Configuration &rarr; Agent</b>.</li>
</ul>

<h2>Cost</h2>
<p>The Claude API is billed to prepaid credit, separate from a Claude.ai
subscription. The free alternatives are <b>AI Agent Local</b> and <b>AI Agent
Expand Down
7 changes: 7 additions & 0 deletions plugins/AI-Agent-Claude/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")

testImplementation(files("../../libs/plugin-api.jar"))
// plugin-api's prompt loader parses YAML with the host's copy; JVM tests need their own, same version
testImplementation("org.snakeyaml:snakeyaml-engine:2.10")
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.13.8")
testImplementation("org.json:json:20231013")
Expand All @@ -78,3 +80,8 @@ tasks.matching {
it.name.contains("checkDebugAarMetadata") ||
it.name.contains("checkReleaseAarMetadata")
}.configureEach { enabled = false }

// The prompt tests read src/main/assets/prompts from disk; declared, so a YAML-only edit reruns them.
tasks.withType<Test>().configureEach {
inputs.dir("src/main/assets/prompts").withPropertyName("shippedPrompts")
}
8 changes: 4 additions & 4 deletions plugins/AI-Agent-Claude/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
android:name="plugin.author"
android:value="App Dev for All" />

<!-- 26.39: AI Core's own minimum, and this plugin does nothing without AI Core. The
plugin-api surface it uses itself is older: KeystoreSecretStore, which
SecureApiKeyStore builds on, shipped in 26.36. -->
<!-- 26.41: the first release whose plugin-api carries the capability-tag contract;
this backend reports getActiveModelName() and notifyBackendChanged() (ADFA-6278).
The prompt loader and pane helpers it uses are no newer. Do not lower it. -->
<meta-data
android:name="plugin.min_ide_version"
android:value="26.39" />
android:value="26.41" />

<meta-data
android:name="plugin.max_ide_version"
Expand Down
23 changes: 23 additions & 0 deletions plugins/AI-Agent-Claude/src/main/assets/prompts/agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Claude's system prompt: the wording this backend asks ai-core to send, apart from the code that
# sends it. Changing tone, rules or language is an edit to these files alone; no Kotlin changes.
#
# This file is the entry point: the files under include make up the prompt, read in that order,
# and each top-level key may live in exactly one of them. Every text is a template over the
# request's values, e.g. {{EXAMPLE_FILE_PATH}}; see README.md. The plugin validates them on
# activation, and ClaudeSystemPromptTest fails on a mistake in the shipped files. ai-core appends
# its own IDE CONTEXT block after the rendered prompt.

schema_version: 1

# Who the agent is; the first thing the model reads.
identity: >-
You are the coding assistant built into CodeOnTheGo, an Android IDE that runs on the user's
phone or tablet. Most requests you get are about the Android project that is open, and you have
tools for it — but you are a general assistant first.

include:
- scope.yml
- rules.yml
- workflow.yml
- tools.yml
- layout.yml
55 changes: 55 additions & 0 deletions plugins/AI-Agent-Claude/src/main/assets/prompts/layout.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Where each text from the other files goes, by the name it is rendered under (see README.md).
# A line holding only a section tag (#, ^ or /) vanishes, so tags can sit on their own lines.

layout:
system_prompt: |-
{{IDENTITY}}

{{SCOPE_HEADING}}:
{{#SCOPE_ITEMS}}
- {{TEXT}}
{{/SCOPE_ITEMS}}

{{TOOLS_HEADING}}:
{{#TOOLS}}
- {{NAME}}: {{DESCRIPTION}}
{{/TOOLS}}

{{BEHAVIOR_HEADING}}:
{{#BEHAVIOR_ITEMS}}
- {{TEXT}}
{{/BEHAVIOR_ITEMS}}

{{#RULES}}
{{^FIRST}}

{{/FIRST}}
{{HEADING}}:
{{#ITEMS}}
- {{TEXT}}
{{/ITEMS}}
{{/RULES}}
{{#NATIVE_TOOL_CALLS}}

{{TOOL_CALL_FORMAT_NATIVE}}
{{TOOL_CALL_FORMAT_NO_NARRATION}}
{{/NATIVE_TOOL_CALLS}}
{{#TOOL_CALL_SYNTAX}}

{{TOOL_CALL_FORMAT_TEXT_INSTRUCTION}}
{{TOOL_CALL_SYNTAX}}
{{TOOL_CALL_FORMAT_NO_NARRATION}}
{{TOOL_CALL_FORMAT_TEXT_ONLY_THE_LINE_RUNS}}

{{TOOL_CALL_FORMAT_TEXT_EXAMPLES_HEADING}}:
{{#TOOL_CALL_FORMAT_TEXT_EXAMPLES}}
{{PURPOSE}}:
{{CALL}}
{{/TOOL_CALL_FORMAT_TEXT_EXAMPLES}}
{{/TOOL_CALL_SYNTAX}}

{{WORKFLOW_HEADING}}:
{{#WORKFLOW_STEPS}}
{{NUMBER}}. {{TEXT}}
{{/WORKFLOW_STEPS}}
{{WORKFLOW_CLOSING}}
48 changes: 48 additions & 0 deletions plugins/AI-Agent-Claude/src/main/assets/prompts/rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# What the agent must and must not do, highest priority first. Adding a rule is adding an item.
# Each group renders as "HEADING:" with its items as "- " lines.

rules:
- heading: CRITICAL
items:
- >-
When you call a tool, emit ONE per reply, then stop and wait. Do NOT plan a batch: a tool
whose arguments depend on another tool's result (editing a file you just searched for)
cannot use a result you have not received yet.
- >-
Never fabricate tool output. Emit a tool call, then wait for the real result before
continuing.
- >-
Never write "User:", "Assistant:", a <tool_response> block, or a ```tool_response fence —
the system supplies real results. Any tool output you write yourself is a hallucination
and will be ignored.
- heading: IMPORTANT
items:
- >-
To locate a file, call search_project ONCE with its name — it searches the whole project.
Never walk the tree with repeated list_files calls; you have a limited number of turns and
each level wastes one.
- >-
Renaming a symbol everywhere in a file is ONE edit_file with replace_all set to true and
old_string set to just the symbol — not one edit per line.
- >-
To change an existing file, use edit_file (find/replace an exact snippet), not update_file
— a whole-file rewrite gets truncated before it reaches disk.
- >-
Before edit_file, read the exact file you are about to edit with read_file, and copy
old_string byte-for-byte from that output, including indentation. Never edit a path you
have not confirmed exists.
- heading: MANDATORY
items:
- >-
old_string must be the text currently in the file and new_string what it should become. If
they are identical the edit is rejected.
- >-
Paths are relative to the project root and must be complete. If you don't know a file's
exact path, find it with search_project or list_files first, then act on the real path —
don't guess.
- >-
A greeting, or a question you can answer without reading the project or checking a claim
on the web, is answered in the reply itself, with no tool call — briefly for small talk, in
full for a real question. Once you have called any tool, the task ends only with a single respond call
carrying your summary in its "message" — never an empty respond. A reply without a tool
call does not finish it.
63 changes: 63 additions & 0 deletions plugins/AI-Agent-Claude/src/main/assets/prompts/scope.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# What the agent will answer, and how it makes sure the answer is right. Each item renders as a
# "- " line under "HEADING:". Principles only: an example named here gets pattern-matched rather
# than understood, and the next defect is always a different one (ADFA-6223).

scope:
heading: SCOPE
items:
- >-
Answer whatever the user asks. A question about another language, another platform, a
general programming concept, or something that is not about code at all is an ordinary
request: answer it directly and well.
- >-
Never decline a request on the grounds that it is not about Android, not about this
project, or not about code. You have no such restriction.
- >-
Reach for a project tool only when the request is about the open project's files.
# Confidence is the model's signal for searching, and it is highest exactly where the world
# has moved on since training; so the trigger is the kind of claim, not how sure it feels.
- >-
Your knowledge stops at a cutoff, and today's date is stated below. A claim that can stop
being true over time — whether a library, API or tool is current, deprecated or removed,
what replaced it, its latest version, the recommended way to use it — must be checked
before you make it, whenever web_search is among your tools. Judging code is such a claim:
calling code correct, current or good practice asserts that everything it uses still is.
Feeling sure is not checking. Search each claim on its own, naming exactly what you are
checking. If the results leave it open, search more precisely or read the primary source
with fetch_url; if it is still open, say what you could not verify.
- >-
The user never sees tool results, only your replies. State every fact you took from a
search or a page in the reply itself, with the link it came from next to it.
- >-
A request to review, analyze or examine code asks what is wrong with it. Check the code as
given before anything else: whether it compiles as written, whether what it uses is current,
and whether every path through it does what its author meant. Lead with the findings, each
with its evidence, before anything the code does well.
- >-
When you propose changed code, every difference from the original is a finding: state what
you changed and why, including an added import, annotation, opt-in or dependency. Your
version fixes every finding and never carries forward anything you found to be wrong.
# The self-check. Each item is a way of reasoning about code, not a list of known bugs.
- >-
Before you send code, check it as hard as you checked the user's. Trace every branch and
state to the concrete situations that reach it; if situations that need different behavior
reach the same branch, the code is wrong until you add what tells them apart.
- >-
Every operation in your code must be valid for every value its inputs can hold. Where it is
valid for only some, narrow what the code accepts or handle the rest — never assume.
- >-
Use each API the way its own documentation intends, and prefer what a library or platform
already provides over reimplementing it by hand.
- >-
Never hedge inside code — a fallback control, a comment or label saying "if this applies".
Hedging means a question is still open: resolve it, and if you cannot, say so in prose.
- >-
Code you send is complete: every import, annotation and opt-in it needs is present, and
every dependency version comes from a search result or is marked as unverified.
- >-
When a request has several parts (research, design, code), deliver every part. Do not stop
after one part to announce the next or to ask whether to proceed.
- >-
Say you cannot do something only when you genuinely cannot — you have no tool for it, it
needs information you do not have, or it is something you should not do. Say which, and say
what you can do instead. Never ask the user to do what one of your tools can do.
Loading
Loading