External Provider Support
Multi-Provider Architecture
Memo connects to external LLM APIs alongside local models:
- Supported Providers (16
ProviderType values): OpenAI, Google Gemini, xAI Grok, Anthropic Claude, OpenRouter, Groq, Ollama, bundled llama.cpp, a generic Custom (any OpenAI-compatible endpoint), Custom (Anthropic-compatible) (any Anthropic Messages API-shaped endpoint — e.g. your own proxy), OpenCode Zen (pay-as-you-go, some models free), OpenCode Go (subscription), Kilo Code (app.kilo.ai — pay-as-you-go, some models free), Cline (api.cline.bot, new in v4.6.0 — OpenAI-compatible, with a per-model free/paid catalog like OpenRouter, Kilo and OpenCode Zen), and the two CLI providers (Claude Code, Codex). The gateway-style providers let you pick from a live model list instead of typing a model name by hand, with free models sorted to the top and marked with a green checkmark.
- Add Provider, organized around cost (v4.6.0): providers with a genuine free tier carry a green badge and sit in their own group, apart from paid ones. For the four providers with a real free-model catalog (OpenRouter, Kilo Code, OpenCode Zen, Cline) a Pick a free model button picks a working $0 model in one click.
- Subscriptions (v4.6.0) — not a separate provider type but a pre-configured
custom provider named Subscriptions (internal/app/subs.go): sign in once under Settings › Subscriptions with an Antigravity, Claude or Codex account and every model that account offers is selectable in the chat's model selector, /model and the local /v1 gateway (subs/<model>), no API key. The work is done by CLIProxyAPI (MIT), bundled inside Memo and never downloaded at run time: its pinned release and SHA-256s live in internal/cliproxy/PINNED.txt, are checked at build time and again before every start, and it listens on loopback only with a random key. It replaces the Beta gemini-sub / claude-sub providers, whose own OAuth clients the vendors kept breaking. Vendors may not allow third-party clients on these accounts — the page says so.
- Model picker (
widgets/model_picker.dart): a height-capped, scrolling panel with search (over seven rows), foldable vendor sections, family logos and readable names ("Gemini 3.7 Flash" + a "High" tag); the raw id is what is stored. The chosen model survives a restart.
- Remaining allowance: a percentage badge and time-to-refill per model. Antigravity reports it per model; Codex and Claude meter the account in windows (Claude 5 h / 7 d) and the tightest window applies to all of that vendor's models. Quota never blocks a request — figures come from a cache and refresh in the background.
- Usage-limit card: when a turn dies because the allowance ran out, the chat shows a card with a countdown, an "automatically continue when it resets" checkbox (default on, remembered per device) and "Continue now"; at the refill time Memo types
continue for you. A quota_low warning appears once per window when 10% or less is left. Works while Memo is open; the card is not persisted, and CLI-agent chats, WhatsApp and the task loop are not covered (the loop retries on its own timer).
- Prompt cache and the window (measured live, 2026-10-08): through the sidecar, Claude on Antigravity cached 4942 of 4952 prompt tokens on a repeated prefix; Codex cached 3840 of 4470 on an identical repeat but reported 0 for a request that only shared the prefix — its cache hits are irregular, not absent. Memo keeps the prompt prefix byte-stable (the time block and working-set digest ride on the new message) and records whatever the account reports in Usage Stats. The sidecar publishes no context length for any model (neither
/v1/models nor the Gemini-style catalogue carries one), so Memo's window for a Subscriptions model is a rule of thumb: claude-* 200K (conservative), gemini* 1M, anything else — the gpt-*/Codex models and gpt-oss — 128K; a context size configured on the provider always wins over this rule. That is a budget Memo will not exceed, not a measured limit of the model.
- Images: image models are found by asking the endpoint, not by id —
POST /images/generations for text-to-image, /images/edits when you attach a picture.
- Automatic picture routing (v4.6.0): ask any chat — in the app, the terminal, Telegram or WhatsApp — for a picture and that one message goes to an image model; the chat's own model is never switched, and the next message is answered by it again. The wording check (Turkish and English) is deliberately conservative: code, tables, charts, how-to questions and pasted documents stay with the text model, and a photo with a question about it is described, not edited. On a Subscriptions account the picture is drawn by that account's own image model — the vendor of the model in use first (a Codex chat draws with Codex's, an Antigravity chat with Antigravity's) — never the default. Every other provider uses the default image model chosen under Settings › API Providers › Image generation (provider + model; both or neither). The same card holds the on/off switch (when off, an image model is used only through
/image or by selecting it). Unattended work (task loop, Code Mode) is never rerouted. The source of truth is config.yaml image: and GET/PUT /api/image/config.
- Open a picture full-window (v4.6.0): tap a picture in the chat (or its enlarge button) to zoom with the buttons, mouse wheel, pinch, double-tap or
+ / - / 0, pan while zoomed, see its details (resolution, aspect ratio, format read from the file, size) and download it through the platform save dialog on desktop, mobile and web.
- Claude and Gemini now support real tool-calling (previously entirely missing on both — an agent/task-loop turn on either provider silently couldn't use tools at all). Both round-trip single and parallel tool calls correctly per each vendor's own wire format.
- Claude Code / Codex CLI as chat providers (beta): instead of an API call, Memo shells out to a locally installed
claude/codex CLI. Per-chat (not app-wide), runs as a real untimed background job, uses the CLI's own no-prompt permission mode, and its own / slash commands surface in Memo's command popup. No memory/identity context is sent — the CLI manages its own session.
- Provider Interface: Common
Provider interface with ChatCompletion, ChatCompletionStream, ListModels
- Fallback Chain: Router tries providers in order; auto-disables after 3 consecutive failures; health-check re-enables on recovery
Encrypted Key Management
- AES-256-GCM Encryption: API keys encrypted with machine-derived key (
/etc/machine-id)
- Key Storage:
data/providers.json with encrypted key values
- Test Connection: Built-in test button validates connectivity before saving
Frontend Provider UI
- API Providers Tab: Settings tab for adding/editing providers
- Configuration Dialog: Provider type selector, API key input (masked), base URL, model dropdown
- Active Provider Selection: Choose which provider is active for chat
- Image-output models on OpenRouter (v4.6.0): a model that only produces images is routed to OpenRouter's image endpoint and the picture arrives as the reply (before, every turn 404'd into "all providers failed"). Which models are image models is read from OpenRouter's own catalog; background jobs that need text (chat titles, memory extraction) never call them.
- A bad key no longer shows "connected", and a lone active provider is never locked out for 5 minutes after three errors — back-off only applies when there is another provider to fall back to.
- Sampling parameters are withdrawn when refused: Claude Opus 4.7+ rejects
temperature and friends; Memo notices the refusal once and stops sending them for that provider. The same retry-and-latch valve handles stream_options.include_usage and cache_control on endpoints that do not know them.
Source document