Providers
ModLens does not depend on any single vision service. Ten sources of vision in total: six built-in providers, any one of which is enough, plus four local agent CLIs whose logins can be reused.
Built-in providers
| Provider | What it needs | Speed per read | Good for |
|---|---|---|---|
gemini-api |
a free Gemini API key (3 minutes, no card) | 5-10s | the recommended default |
openai |
any OpenAI-compatible endpoint (key + baseUrl + model) | 5-10s | qwen-vl, GLM, self-hosted gateways |
anthropic |
an Anthropic API key | 5-10s | machines already holding one |
antigravity-cli |
the free agy CLI, one browser sign-in, no key |
15-45s | zero-signup starts |
claude-cli |
a signed-in Claude Code | 20-45s | riding your existing Claude subscription |
kimi-cli |
a signed-in Kimi Code | 20-45s | riding your existing Kimi subscription, named explicitly |
kimi-cli runs only when named, never as a silent failover peer. Default with nothing configured: antigravity-cli.
Failover
Without a pinned provider, every configured engine forms one failover chain: configured API providers first (5-10s), then the agent CLIs (15-45s). The first good result wins, and meta.attempts records every attempt so a fallback is never silent.
modlens config set provider <name> states a preference (the chain still backs it up). -p <name> pins exactly one with no fallback.
openai is a universal socket, not just OpenAI
Any endpoint speaking the OpenAI chat-completions protocol with image input plugs straight in. That covers most of the vision-model world:
modlens config set openai.baseUrl https://dashscope.aliyuncs.com/compatible-mode/v1 # qwen-vl
modlens config set openai.apiKey <key>
modlens config set openai.model qwen3-vl-plus
apiKey (and the matching env var) also accepts a comma-separated list. ModLens rotates to the next key after authentication, rate-limit, or quota failures. Network, 5xx, and parse failures skip remaining keys and keep provider failover.
The same three keys work for GLM’s open platform, SiliconFlow, OpenRouter, a self-hosted vLLM/Ollama, or any gateway of your own.
Reusing what your machine already has
Two more sources of vision need zero new keys, each behind one explicit consent recorded in config:
- The harness you are talking in right now. Running inside Claude Code with a subscription signed in?
claude-clireads images through it out of the box. The install flow asks the same question for whichever harness you install into. - Every other agent CLI on the machine.
modlens doctordiscovers them, you grant per harness, and they join the same failover chain with no priority over your own keys. Every reused read is labeled inmeta.warningswith whose quota it spent, so nothing is ever silently billed:
| Reused CLI | What it needs | Grant with | Rides as |
|---|---|---|---|
| Codex | a signed-in Codex CLI with a vision model | config set reuse.codex true |
agent lane, 15-45s |
| OpenCode | a vision model configured in OpenCode | config set reuse.opencode true |
agent lane, 15-45s |
| Pi | model credentials held by Pi | config set reuse.pi true |
an API key upgrades to the 5-10s inline lane, OAuth drives Pi itself |
| Grok | a signed-in Grok CLI (SuperGrok) | config set reuse.grok true |
agent lane, 15-45s |
Machines behind a proxy set HTTPS_PROXY or modlens config set proxy <url> and the API providers route through it.
Provider setup recipes
antigravity-cli (default, free, no key)
Needs Antigravity CLI installed and signed in:
curl -fsSL https://antigravity.google/cli/install.sh | bash
agy # user must complete browser sign-in themselves, then exit
Any free Google account works; no Google AI Pro needed. Sign-in cannot be automated, ask the user to run agy once.
gemini-api (free key, fastest free route, 5-10s)
- The user creates a key at https://aistudio.google.com (three minutes, no credit card, free tier does not expire).
- Store it either way:
modlens config set gemini-api.apiKey <key>
# value omitted: a hidden prompt, so the key skips argv, shell history, and this chat
modlens config set gemini-api.apiKey
Offer the hidden prompt first when the user is at their own terminal. Most users paste the key into the chat because it is convenient, and that works too: take it and store it. The prompt is for the ones who would rather not.
Default model gemini-3.6-flash has vision on the free tier (about 10-15 requests/min, 1500/day). Free-tier data may be used by Google to improve products; mention this if the user handles sensitive images.
openai (any OpenAI-compatible multimodal endpoint)
Needs three values. Example for DashScope qwen:
modlens config set openai.baseUrl https://dashscope.aliyuncs.com/compatible-mode/v1
modlens config set openai.apiKey <sk-key>
modlens config set openai.model qwen3.6-27b
baseUrl is required, official OpenAI included (https://api.openai.com/v1): this route serves any compatible endpoint, and guessing one would send a key meant for another vendor, and the image beside it, somewhere the user never named. The model must be multimodal; text-only models will fail or hallucinate.
This route enforces nothing server-side by default, so a weaker model can answer with half the contract and the run fails with an explicit error. If that happens, ask the gateway to enforce it:
modlens config set openai.structuredOutput true
The contract goes out as response_format: json_schema in strict form, derived from the schema modlens checks against. Off by default because a gateway without structured-output support answers 400 for the field, so turn it back off if the endpoint refuses it. Turning thinking off (below) makes the shape failures more likely, so the two often go together.
anthropic (Claude API key)
modlens config set anthropic.apiKey <sk-ant-key>
Default model is Claude Haiku (claude-haiku-4-5-20251001). Schema is enforced through a forced tool call.
The ANTHROPIC_BASE_URL trap is defused. modlens used to bind that variable to anthropic.baseUrl field by field, so a shell that routed Claude Code through a text-only gateway silently sent vision requests there too, even beside a key set in the config file. The moment the file names anthropic, the file is this route’s whole source and that variable no longer reaches it: set anthropic.baseUrl when you do want a different endpoint. ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL still configure this route on their own while the file says nothing about anthropic, both halves coming from the same place. A run caught between the two, with the variable set and the file naming anthropic without a baseUrl, refuses and prints the command that keeps the endpoint you were using.
kimi-cli (Kimi Code login, no key)
Rides an existing kimi sign-in, so it spends the user’s Kimi Code subscription
rather than a key. Install from https://moonshotai.github.io/kimi-code/, run
kimi once and /login, then:
modlens config set provider kimi-cli
modlens config set kimi-cli.model <alias> # optional; kimi's own default otherwise
Naming it is what turns it on. Unlike the other CLI routes it never joins the failover chain on its own, because it spends a subscription and installing the CLI is not agreement to spend it.
The model alias is kimi’s, in <provider>/<model> form as kimi provider list
shows it, and it has to accept image input. This route enforces no schema (the
CLI has no --json-schema), so the contract travels as a filled-in JSON
template and a weaker model can answer with half of it; -p gemini-api is the
fallback when that happens.
One implementation note worth knowing if you debug it: modlens runs kimi with
skill discovery pointed at an empty directory. Otherwise kimi can find the
modlens skill in the shared skill directories and read the image by calling
modlens, which is modlens calling itself.
claude-cli (Claude Code login, no key)
Rides an existing claude sign-in, so it costs the user’s Claude subscription quota, not a separate API bill. Requires Claude Code installed and logged in (claude --version to check). Runs with --allowedTools Read only. Local image files only; for remote URLs use gemini-api instead. Default model alias haiku.
modlens config set provider claude-cli # make it the default if the user wants
Every config key is in Configuration. Flags and default models are in the CLI manual. Remote URL fetching is in Security.