Automatic Gemini API key rotation for modlens on DeepSeek Harness (dsh) — when one key hits its quota (HTTP 429), the read automatically switches to the next key in the pool and retries, with zero intervention. Works everywhere modlens reads an image: the dsh GUI paste flow, the modlens_read_image tool, and the CLI.
⚠️ Unofficial: this patches the installed modlens package. The patch is wiped by any modlens upgrade; patch.ps1 re-applies it in one command. If modlens upstream ever ships native multi-key support, drop this project.
Why
modlens reads images through a single Gemini API key stored in ~/.modlens/config.json. Free-tier keys hit daily quota (429 RESOURCE_EXHAUSTED) / rate limits quickly, and modlens has no retry or failover for that. This project adds:
a key pool (~/.modlens/api-keys.json) — list, add, rotate, inspect
an engine patch — 429 triggers an automatic switch to the next pool key, persisted to the config, then a retry of the same request (up to 8 rotations)
a CLI wrapper — 429 → rotate + retry; 5xx → backoff retry on the same key
a re-patch script — re-applies the engine patch after a modlens upgrade
Requirements
DeepSeek Harness (dsh) with the modlens plugin installed: npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@<version>
Node 22.19+ (modlens requirement)
PowerShell (Windows) — the scripts are .ps1
Install
Clone this repo (or copy scripts/), then:
1 2 3 4 5 6 7 8 9
# 1. copy scripts next to the modlens config Copy-Item scripts\* "$env:USERPROFILE\.modlens\" # or link: New-Item -ItemType SymbolicLink ... (per-file)
# 3. seed the key pool (one command per key) powershell -ExecutionPolicy Bypass -File"$env:USERPROFILE\.modlens\rotate.ps1" add <GEMINI_API_KEY>
The active key is the one modlens uses: if you already configured a key via modlens config set gemini-api.apiKey, add it to the pool too so rotation can cycle back to it.
Usage
1 2 3 4 5 6 7 8
# manage the pool rotate.ps1 list # show pool, >> marks the active key rotate.ps1 status # pool size + active key (masked) rotate.ps1 rotate # switch to the next key (persists) rotate.ps1 add <key> # add a key to the pool
# read an image with automatic rotation ml.ps1 -Image <path-or-url>
The engine patch makes rotation automatic for every read path (GUI paste, modlens_read_image tool, CLI), so rotate.ps1 rotate is mainly for manual override or diagnosis.
If you use a proxy
modlens itself supports it — no changes needed here:
1
modlens config set proxy http://127.0.0.1:<port>
Note: Google’s Gemini API is region-restricted. A proxy exit in an unsupported region (e.g. mainland China, Thailand) fails with 400 User location is not supported for all keys — rotation cannot fix that; switch the proxy node instead.
It detects whether the patch is still present; if a new version changed the targeted code and the hunks no longer match, it reports which hunk failed and tells you to file a new patch.
How it works
modlens ships two components in one package:
dsh/index.js — the dsh plugin shell: registers modlens_read_image, handles paste-to-path. It contains no image-reading logic; it spawns a child process running the CLI engine.
dist/main.js — the CLI engine: reads the image, calls Gemini (executeGeminiApi), parses structured JSON. Every read path funnels through this file.
The patch lives in executeGeminiApi: a single request becomes a loop that rotates the pool on 429. See PATCH.md for the exact change.
Security
Keys are stored in plain text at ~/.modlens/api-keys.json (same trust level as modlens’s own config.json, which also stores the key in plain text). Do not share your home directory.
This repo contains no keys, no credentials, no machine-specific paths — all paths derive from $env:USERPROFILE.
openviking.storage.viking_vector_index_backend - ERROR - Error reading existing record before partial update: Strings must be encoded before hashing openviking.storage.collection_schemas - ERROR - Failed to write to vector database: Strings must be encoded before hashing