Providers

Emet speaks to four kinds of provider: OpenAI, Anthropic, Google Gemini and anything with an OpenAI-compatible API (Ollama, LM Studio, Groq, vLLM and so on). You configure them in admin/cms.yaml, and each person using the CMS either uses a key you’ve put in that file or adds their own in the AI Settings modal.

Turning AI on#

AI is off until cms.yaml has an ai: block. The shortest one that works is:

ai:
  enabled: true

That gets you the three hosted providers with their default models and Gemini as the default. Nothing will respond until somebody adds a key, but the AI tab, toolbar button and shortcuts all appear.

A fuller example, with every key Emet actually reads:

ai:
  enabled: true
  providers:
    - type: gemini
      model: gemini-3.5-flash
      apiKey: "AIza..."              # optional site-wide key
    - type: anthropic
      model: claude-sonnet-4-6
    - type: custom
      endpoint: "http://localhost:11434/v1"
      model: llama3
  defaultProvider: gemini
  features:
    chat: true
    inline: true
    seoOptimize: true
    imageGeneration: true
    translation: true
    pageBuilder: true
  brandVoice: |
    Plain English, second person, no marketing adjectives.
    British spelling. Short paragraphs.
  actions:
    - id: tldr
      label: Add a TL;DR
      prompt: Add a one-paragraph TL;DR to the top of this text, then return the whole text.
      scope: body
  shortcuts:
    inlineAction: Mod-J
    openChat: Mod-Shift-A

If you leave providers out you get all three hosted providers; if you list some, you get only those. defaultProvider falls back to the first in the list. The setup wizard writes this block for you if you tick Enable AI features on its Integrations step, with the default model filled in for each provider you pick.

Providers and models#

Type Default model Built-in list Image models
gemini gemini-3.5-flash gemini-3.1-pro-preview, gemini-3.5-flash gemini-3-pro-image-preview (default), gemini-2.5-flash-image
openai gpt-5.5 gpt-5.5, gpt-5.5-pro, gpt-5.2, gpt-5 gpt-image-2
anthropic claude-sonnet-4-6 claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5 none
custom default whatever the endpoint reports none

The built-in list is only a starting point. Once a key is saved (or you click Refresh next to the model dropdown), Emet asks the provider for its current models and caches the answer in your browser, so the dropdown shows what your account can actually use. OpenAI’s list is filtered to chat models; Gemini’s to models that support generateContent. There’s also a + Custom model… option at the bottom of the dropdown for typing an ID that isn’t listed.

Which model gets used, in order of precedence: the one you picked in AI Settings, then the model in cms.yaml, then the default above. Your pick is remembered per provider in your browser.

API keys#

Keys can come from two places. A site-wide key is the apiKey on a provider in cms.yaml: everyone who opens the CMS can use AI without setting anything up, and the provider shows a Site key badge in settings. A personal key is one you paste into AI Settings (the cog in the AI panel header); it’s stored in your browser’s localStorage and overrides the site key for you only.

The settings modal has a Test button that validates a key without saving it and a Save button that validates and keeps it, then fetches the model list. There’s a Get key link next to each hosted provider pointing at the right console.

Warning
A site-wide key is a key in a Git repository. If the repository is public, so is the key. The wizard warns you about this, but it’s worth repeating: for a public site, leave apiKey out and let people add their own.

Requests go directly from the browser to the provider’s API. For Anthropic that means Emet sends the anthropic-dangerous-direct-browser-access header, which is their opt-in for exactly this. There’s no server-side proxy, and the endpoint key is ignored for the three hosted providers, so a cms.yaml can’t quietly redirect your key somewhere else.

Emet also rate-limits itself on the client side, at 60 requests a minute for OpenAI and Gemini, 50 for Anthropic and 100 for custom endpoints. If you hit it you’ll get a “Rate limited” error with a countdown rather than a bill.

Gemini key types#

Google issues two kinds of key, and Emet tells them apart by prefix:

  • AIza… keys come from Google AI Studio. Requests go to generativelanguage.googleapis.com.
  • AQ.… keys are Vertex AI Express Mode keys from Google Cloud. Requests go to aiplatform.googleapis.com and bill your Cloud account, which is the way to spend Google One or Developer Program credits on this.

The settings modal shows an AI Studio or Vertex Express badge next to the key. Note that Vertex Express keys can’t list models, so for those Emet substitutes a hard-coded list of older models (gemini-2.5-flash, gemini-2.5-pro, gemini-2.0-flash, gemini-1.5-pro, gemini-1.5-flash). Use + Custom model… if you want something newer.

Custom providers#

The custom type works with any endpoint that accepts OpenAI-style /chat/completions requests. The only thing it needs is endpoint; a key is optional, which suits a local Ollama or LM Studio server:

ai:
  providers:
    - type: custom
      endpoint: "http://localhost:11434/v1"
      model: llama3
  defaultProvider: custom

For the model list Emet tries GET {endpoint}/models first and, if that fails, Ollama’s /api/tags. Custom providers never do image generation, whatever sits behind them.

Brand voice#

ai.brandVoice is a block of free text that’s added to the system prompt of every AI call: chat, inline actions, page builder, translation, the lot. It’s the one place to say “we don’t use exclamation marks” and have it stick. Keep it short; it goes out with every request.

Custom actions#

ai.actions adds your own entries to the toolbar AI menu, next to Improve Text. Each has an id, a label, a prompt and an optional scope:

Key What it does
id A unique key. Anything you like.
label The text in the menu.
prompt Your instruction. Emet appends the text to work on and asks for only the result back.
scope selection (the default) works on selected text, or the whole body if nothing’s selected. body always works on the whole body.
icon Accepted but not used for anything yet.

The result replaces the selection, or the whole body, with no confirmation step. Undo works as usual.

Feature toggles#

Everything under features defaults to on. Setting one to false hides the corresponding UI:

Key Hides
inline The toolbar AI menu and the AI button on the selection toolbar. Since the toolbar menu is also where image generation, translation, SEO and the page builder live, this takes those with it.
imageGeneration Generate Image and the Edit with AI buttons on images.
translation Translate in both the toolbar menu and the selection menu.
seoOptimize The SEO quick actions in chat and the toolbar menu.
pageBuilder Build Page in chat and Page Builder in the toolbar menu.
chat Nothing, currently. The chat tab is tied to ai.enabled.

Shortcuts#

ai.shortcuts.inlineAction (default Mod-J) opens the inline actions menu on a selection, and ai.shortcuts.openChat (default Mod-Shift-A) switches the right panel to the AI tab. Both use Tiptap’s key notation (Mod is Ctrl on Windows and Linux, Cmd on a Mac) and both only work while the visual editor has focus. See Keyboard shortcuts for the rest.