21 Commits

Author SHA1 Message Date
alexpolo1
e50f99c975 security: remove keystore and hardcoded signing credentials
Some checks are pending
CI / Build APK (push) Waiting to run
CI / Install + Smoke Test (emulator) (push) Blocked by required conditions
- Remove pixel10.keystore from repository
- Move signing passwords to local.properties (gitignored)
- Add *.keystore and *.jks to .gitignore
- build.gradle.kts now reads credentials from local.properties

After pulling, create local.properties with:
  KEYSTORE_FILE=pixel10.keystore
  KEYSTORE_PASSWORD=<your-password>
  KEY_ALIAS=pixel10
  KEY_PASSWORD=<your-password>
2026-03-01 09:09:15 +01:00
alexpolo1
c00368c303 Commit keystore to repo for consistent APK signing across all CI builds
Simplifies signing: the keystore is stored in the repo so every release
workflow run uses the same certificate. This fixes "App not installed"
when updating via GitHub releases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:36:10 +01:00
alexpolo1
268e0176ae Fix keystore base64 decode in CI (use printf + ignore-garbage)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:33:22 +01:00
alexpolo1
04135d7539 Add consistent APK signing so updates install without uninstalling
- Generate pixel10.keystore and store as KEYSTORE_BASE64 GitHub secret
- Release workflow decodes keystore and signs every APK with same key
- build.gradle.kts: signingConfig reads from env (CI) or local keystore
- Bump versionCode 8 / versionName 1.8.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:28:39 +01:00
alexpolo1
ef2180a8dd Bump versionCode to 7 / versionName to 1.7.0 for proper update installs
Android requires versionCode to increase for update-over-top installs.
Without this, reinstalling would say "App not installed" on some devices.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:23:40 +01:00
alexpolo1
9ba9359400 Add in-app chat with streaming, settings UI, and request indicator
Chat:
- ChatActivity with RecyclerView message list (user/AI bubbles)
- Streams tokens directly from LiteRT model via generateStreaming()
- Maintains full conversation history (multi-turn context)
- Respects auto_system_prompt setting from SharedPreferences
- "Chat" button in MainActivity, enabled only when server is running
- " Generating…" indicator while model is thinking
- Log on main screen unchanged — still shows all requests

Server:
- Settings card: temperature slider, max tokens, system prompt toggle
- " Processing request…" indicator in status card during inference
- onActiveRequest callback wired through service → activity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:21:49 +01:00
alexpolo1
bdff8f36b7 Fix streaming, add thread safety, settings UI, and request indicator
Fixes:
- Streaming was bypassed for all requests (system prompt auto-inject made
  messages.size always > 1). Stream=true now routes directly to generateStreaming
  before multi-turn check
- LiteRTModel: add Mutex to serialize Engine calls (not thread-safe)

New features:
- Settings card: temperature slider, max tokens, agent system prompt toggle
  (all saved to SharedPreferences, applied on server start)
- Active request indicator: " Processing request…" shown in status card
  while inference is running
- onActiveRequest callback from AIApiServer → ApiServerService → MainActivity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:17:41 +01:00
alexpolo1
6cd531cd58 Upgrade to LiteRT-LM runtime for Gemma 3n E4B support
- Add com.google.ai.edge.litertlm:litertlm-android:0.9.0-alpha05 dependency
- New LiteRTModel backend: loads .litertlm files via Engine API with GPU backend
  (CPU fallback if GPU init fails)
- OnDeviceModel.create() priority: LiteRT-LM → MediaPipe → Gemini Nano
- ModelDownloader: updated to Gemma 3n E4B (4.9 GB) and E4B Web (4.3 GB)
  from google/gemma-3n-E4B-it-litert-lm (HuggingFace, license required)
- Download uses Bearer token auth; token saved to SharedPreferences

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:49:22 +01:00
alexpolo1
9d18d30e87 Fix downloads: HuggingFace auth + Gemma 3 1B IT models
Google moved all models to HuggingFace (old CDN returns 404).
All downloads now require a HuggingFace API token.

- Add HF token input field (saved to SharedPreferences, restored on relaunch)
- Download uses Bearer auth header
- Switch to Gemma 3 1B IT (litert-community) — .task format, works with
  current MediaPipe API, 555 MB Q4 or 1 GB Q8
- Clear auth error messages (401/403 shown to user)
- Gemma 3n E4B/E2B (.litertlm format) requires runtime upgrade — planned next

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:36:05 +01:00
alexpolo1
e4a8fd805a Add multi-model download: Gemma 3n E4B (recommended), E2B, Gemma 2B
- ModelSpec enum with 3 downloadable options from MediaPipe CDN
- Gemma 3n E4B set as recommended default (best coding/reasoning via MoE)
- Gemma 3n E2B as faster/smaller alternative
- Gemma 2B kept as lightest option
- UI shows all 3 download buttons, hides all once any model is installed
- Installed model name shown in status (e.g. "✓ Gemma 3n E4B ready")
- Custom models (DeepSeek Coder, Qwen2.5-Coder) can be manually placed in files dir

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:25:49 +01:00
alexpolo1
95b7c02fd0 Fix background inference: MediaPipe-first, in-app model downloader
Root cause: ML Kit Gemini Nano enforces ErrorCode 30 (foreground restriction)
at the AICore binder level. No foreground service or wake lock can bypass it —
the app's Activity must be the visible top window.

Fix: MediaPipe LLM Inference runs entirely in the app process via the Tensor G5
GPU (OpenCL/Vulkan), with no AICore dependency and no foreground restriction.

Changes:
- OnDeviceModel.create() now tries MediaPipe FIRST, Nano second
- ModelDownloader.kt: downloads Gemma 2B IT GPU INT4 (~1.3GB) from Google's
  MediaPipe model CDN with resume support (Range header)
- MainActivity: "Download Model" card shows download status and progress bar;
  auto-hides once model is present; uses lifecycleScope for coroutine
- Layout: model card inserted between status and port field
- Strings: btn_download_model, model_downloaded, model_not_downloaded

Once the model is downloaded the server accepts requests in the background
indefinitely with no foreground Activity required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:19:14 +01:00
alexpolo1
6147f539ac Add coding agent system prompt, tool set, and /v1/agent endpoint
AgentConfig.kt defines the full agent configuration optimised for Gemini
Nano on the Tensor G5 chip:

System prompt (~700 tokens):
- Five explicit workflow stages: EXPLORE → PLAN → CHANGE → VERIFY → DONE
- Hard rules: one tool per turn, 1-3 sentence replies, 120-line read limit,
  max 3 files per task, patch_file preferred over write_file

Seven tools (OpenAI function-calling format):
  read_file(path, start_line?, end_line?)  — sectioned reads, max 120 lines
  write_file(path, content)               — new files / full rewrites < 80 ln
  patch_file(path, old_str, new_str)      — targeted in-place edits (preferred)
  list_dir(path, depth?)                  — directory structure
  search_code(pattern, path?, include?)   — regex search across files
  run_command(command, cwd?)              — build, test, lint
  task_done(summary, files_changed?)      — explicit completion signal

AIApiServer changes:
- Auto-injects the agent system prompt when the conversation has no system
  message, and auto-injects DEFAULT_TOOLS when the request provides none.
  Makes the server zero-config for any OpenAI-compatible agent client.
- New GET /v1/agent endpoint returns system_prompt + tools + notes as JSON
  so clients like OpenClaw can fetch the config and apply it automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:03:32 +01:00
alexpolo1
ec3beea01e Remove cloud backend — on-device Tensor chip only
Delete GeminiCloudModel.kt entirely. All inference now runs through
Gemini Nano on the Tensor G5 TPU via ML Kit, with MediaPipe as fallback
for custom local model files. No data leaves the device.

- OnDeviceModel.create() reverts to Nano → MediaPipe chain, no apiKey param
- Removed: API key UI, SharedPreferences key storage, cloud model routing
- Removed: thinking mode (cloud-only feature)
- Removed: pixel10-fast / pixel10-thinking model IDs → single "pixel10" model
- /v1/models now reports honest on-device limits (4096 ctx, 1024 output)
- CI smoke test updated: installs APK on emulator and verifies package,
  no inference tests (require real Pixel hardware with Tensor chip)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:58:21 +01:00
alexpolo1
ac24db518f Raise defaults and expose 1M context window to clients
- max_tokens default: 1024 → 8192 (enough for full functions and files)
- thinking mode max_tokens default: 2048 → 16384
- chat() default: 1024 → 8192
- ModelInfo gains context_length (1_000_000) and max_output_tokens fields
  so OpenClaw and other agent frameworks can auto-configure correctly
- /v1/models now advertises full 1M input context for both models

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:49:16 +01:00
alexpolo1
4a79542454 Add tool/function calling — enables use as an agent brain
OnDeviceModel gains a chat() method accepting the full message history and
a list of ToolDef entries. Returns ChatResult which is either a text reply or
a list of ToolCallData the model wants to invoke. Default impl flattens
messages to a prompt so Nano and MediaPipe backends work unchanged.

GeminiCloudModel overrides chat() with native Gemini function calling:
- Converts OpenAI tools to Gemini functionDeclarations
- Handles system messages via Gemini's systemInstruction field
- Converts multi-turn history including assistant tool_calls and tool results
  (role=tool → Gemini functionResponse with name resolved from prior turns)
- Parses functionCall parts in the response and returns ToolCallData list
- Falls back to text content when no function call is present

AIApiServer routes to chat() when tools are provided or the conversation
has more than one turn. Returns finish_reason=tool_calls and the tool_calls
array in the assistant message so OpenClaw / any OpenAI-compatible agent
client can execute tools and feed results back.

ApiModels updated: Message.content nullable, tool_calls and tool_call_id
added to Message, Tool/ToolFunction/ToolCall/FunctionCallDetail added,
tools and tool_choice added to ChatRequest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:45:31 +01:00
alexpolo1
375aaad0fa Add thinking mode — fast and thinking model support
Expose two model IDs matching the Gemini app's modes:
- pixel10-fast: Gemini 2.0 Flash, low latency, no reasoning trace
- pixel10-thinking: Gemini 2.5 Flash, step-by-step reasoning before answering

OnDeviceModel gains generateWithThinking() returning ThinkingResult
(thinking: String, response: String). Default impl delegates to generate()
so Nano and MediaPipe backends work unchanged.

GeminiCloudModel overrides generateWithThinking() to call
gemini-2.5-flash-preview-04-17 with thinkingConfig.thinkingBudget. Parts
with thought=true are collected as the reasoning trace; remaining parts form
the final answer.

ChatRequest gains thinking_budget (0 = fast, >0 = thinking) and model fields.
AIApiServer routes to thinking mode when model name contains "think" or
thinking_budget > 0. Thinking responses include a non-standard thinking field
in Choice alongside the normal content. /v1/models lists both model IDs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:36:47 +01:00
alexpolo1
db48abb0a2 Add GeminiCloudModel, background inference fix, and CI pipeline
- Add GeminiCloudModel: proxies inference to Gemini 2.0 Flash via HTTPS
  using HttpURLConnection (no new deps). Supports both blocking and SSE
  streaming. Works from any context — background, emulator, CI.

- Fix background inference: OnDeviceModel.create() now accepts an apiKey;
  when set, GeminiCloudModel is selected immediately, bypassing Gemini
  Nano's foreground-only restriction. ApiServerService reads the key from
  SharedPreferences at startup.

- Add API key UI: password field in MainActivity saved to SharedPreferences
  before the service starts; disabled while server is running.

- Add GitHub Actions CI (.github/workflows/ci.yml): build job produces a
  debug APK artifact; test job spins up a KVM-accelerated Android 31
  emulator, installs the APK, writes the GEMINI_API_KEY secret into
  SharedPreferences via adb run-as, starts the service, and runs curl
  assertions against /health, /v1/models, /v1/chat/completions, and the
  SSE streaming endpoint.

- Add release workflow (.github/workflows/release.yml): triggered on v*
  tags, builds the APK and creates a GitHub Release with it attached.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:29:47 +01:00
alexpolo1
d0059442f8 Make project production-grade for public release
- Add adaptive app icon with AI chip + signal wave vector design
- Add Apache 2.0 LICENSE file
- Rewrite README with badges, full API docs, Python examples,
  device compatibility table, dependency licenses, and ToS disclaimer
- Extract all hardcoded layout strings to strings.xml
- Add roundIcon support in AndroidManifest
- Add GitHub community files: issue templates, PR template,
  CONTRIBUTING.md, SECURITY.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:03:30 +01:00
alexpolo1
f0a171c35d Fix build: upgrade Kotlin, fix ML Kit/MediaPipe API imports
- Bump Kotlin from 2.0.21 to 2.1.20 for ML Kit genai metadata 2.2.0 compat
- Fix GeminiNanoModel imports: DownloadStatus/FeatureStatus moved to genai.common,
  TextPart/generateContentRequest moved out of .type subpackage
- Fix MediaPipeModel: replace removed setTopK/setTemperature/setRandomSeed
  with setMaxTopK (MediaPipe 0.10.24 API)
- Fix gradlew: remove broken lines that passed GradleWrapperMain as task arg

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:50:21 +01:00
Claude
43fb67d80a Fix API correctness issues found during static audit
- GeminiNanoModel: Rewrite to use actual ML Kit Prompt API
  (Generation.getClient(), checkStatus(), download(), warmup())
- MediaPipeModel: Fix streaming to use sync fallback since
  MediaPipe requires result listener set at build time
- ApiModels: Add @SerializedName("object") for OpenAI compat,
  add "created" timestamp to ChatResponse/StreamChunk
- settings.gradle.kts: Fix dependencyResolutionManagement typo
- MainActivity: Use GradientDrawable.setColor() to preserve
  oval shape on status dot
- Add Gradle wrapper scripts (gradlew, gradlew.bat, jar)

https://claude.ai/code/session_01GvqMLSMmfMR8uz66BFVXX2
2026-02-28 14:27:46 +00:00
Claude
12de200450 Add Pixel10 AI Server — expose on-device AI chip as REST API
Android app that turns a Pixel 10 into a free AI API server by leveraging
the Tensor G5's on-device AI capabilities through an embedded HTTP server.

Key components:
- Dual AI backend: Gemini Nano (ML Kit Prompt API) + MediaPipe LLM fallback
- OpenAI-compatible REST API (chat/completions, completions, models, health)
- NanoHTTPD embedded server with CORS support and SSE streaming
- Foreground service with wake lock for persistent background operation
- Material Design 3 dashboard with live request logging

All inference runs entirely on-device — zero cloud costs, full offline capability.

https://claude.ai/code/session_01GvqMLSMmfMR8uz66BFVXX2
2026-02-28 14:09:41 +00:00