Spaces:
Running
Running
fix(key-rotator): suppress LLM_API_KEY fallback noise in startup logs
Browse filesProviders without dedicated keys were logging "1 key" due to
LLM_API_KEY fallback being included unconditionally. Now only
providers with dedicated env vars log individually; all fallback
providers are summarised in a single line.
Also adds API Key Rotation section to README, HF_TOKEN and
WHATSAPP_ENABLED to Space secrets frontmatter, and key rotation
bullet to features list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- README.md +29 -0
- multi-provider-key-rotator.cjs +22 -5
README.md
CHANGED
|
@@ -20,6 +20,10 @@ secrets:
|
|
| 20 |
description: "Comma-separated Telegram user IDs for access"
|
| 21 |
- name: TELEGRAM_BOT_TOKEN
|
| 22 |
description: "Telegram bot token from BotFather"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
---
|
| 24 |
|
| 25 |
<!-- Badges -->
|
|
@@ -41,6 +45,7 @@ secrets:
|
|
| 41 |
- [πΎ Workspace Backup *(Optional)*](#-workspace-backup-optional)
|
| 42 |
- [π Webhooks *(Optional)*](#-webhooks-optional)
|
| 43 |
- [π Security & Advanced *(Optional)*](#-security--advanced-optional)
|
|
|
|
| 44 |
- [π€ LLM Providers](#-llm-providers)
|
| 45 |
- [π» Local Development](#-local-development)
|
| 46 |
- [π CLI Access](#-cli-access)
|
|
@@ -54,6 +59,7 @@ secrets:
|
|
| 54 |
## β¨ Features
|
| 55 |
|
| 56 |
- π **Any LLM:** Use Claude, OpenAI GPT, Google Gemini, Grok, DeepSeek, Qwen, and 40+ providers (set `LLM_API_KEY` and `LLM_MODEL` accordingly).
|
|
|
|
| 57 |
- β‘ **Zero Config:** Duplicate this Space and set **just three** secrets (LLM_API_KEY, LLM_MODEL, GATEWAY_TOKEN) β no other setup needed.
|
| 58 |
- π³ **Fast Builds:** Uses a pre-built OpenClaw Docker image to deploy in minutes.
|
| 59 |
- π **Cloudflare Outbound Proxy:** HuggingClaw can automatically provision a Cloudflare Worker proxy for blocked outbound traffic such as Telegram API requests.
|
|
@@ -181,6 +187,29 @@ Configure password access and network restrictions:
|
|
| 181 |
| `ALLOWED_ORIGINS` | β | Comma-separated allowed origins for Control UI |
|
| 182 |
| `CLOUDFLARE_KEEPALIVE_ENABLED` | `true` | Set to `false` to disable the automatic Cloudflare KeepAlive worker |
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
## π€ LLM Providers
|
| 185 |
|
| 186 |
HuggingClaw supports **all providers** from OpenClaw. Set `LLM_MODEL=<provider/model>` and the provider is auto-detected.
|
|
|
|
| 20 |
description: "Comma-separated Telegram user IDs for access"
|
| 21 |
- name: TELEGRAM_BOT_TOKEN
|
| 22 |
description: "Telegram bot token from BotFather"
|
| 23 |
+
- name: HF_TOKEN
|
| 24 |
+
description: "HuggingFace token with Write access β enables automatic workspace backup."
|
| 25 |
+
- name: WHATSAPP_ENABLED
|
| 26 |
+
description: "Set to 'true' to enable WhatsApp pairing support."
|
| 27 |
---
|
| 28 |
|
| 29 |
<!-- Badges -->
|
|
|
|
| 45 |
- [πΎ Workspace Backup *(Optional)*](#-workspace-backup-optional)
|
| 46 |
- [π Webhooks *(Optional)*](#-webhooks-optional)
|
| 47 |
- [π Security & Advanced *(Optional)*](#-security--advanced-optional)
|
| 48 |
+
- [π API Key Rotation *(Optional)*](#-api-key-rotation-optional)
|
| 49 |
- [π€ LLM Providers](#-llm-providers)
|
| 50 |
- [π» Local Development](#-local-development)
|
| 51 |
- [π CLI Access](#-cli-access)
|
|
|
|
| 59 |
## β¨ Features
|
| 60 |
|
| 61 |
- π **Any LLM:** Use Claude, OpenAI GPT, Google Gemini, Grok, DeepSeek, Qwen, and 40+ providers (set `LLM_API_KEY` and `LLM_MODEL` accordingly).
|
| 62 |
+
- π **Multi-Key Rotation:** Supply comma-separated key pools per provider (e.g. `ANTHROPIC_API_KEYS=key1,key2,key3`) for automatic round-robin rotation across rate limits.
|
| 63 |
- β‘ **Zero Config:** Duplicate this Space and set **just three** secrets (LLM_API_KEY, LLM_MODEL, GATEWAY_TOKEN) β no other setup needed.
|
| 64 |
- π³ **Fast Builds:** Uses a pre-built OpenClaw Docker image to deploy in minutes.
|
| 65 |
- π **Cloudflare Outbound Proxy:** HuggingClaw can automatically provision a Cloudflare Worker proxy for blocked outbound traffic such as Telegram API requests.
|
|
|
|
| 187 |
| `ALLOWED_ORIGINS` | β | Comma-separated allowed origins for Control UI |
|
| 188 |
| `CLOUDFLARE_KEEPALIVE_ENABLED` | `true` | Set to `false` to disable the automatic Cloudflare KeepAlive worker |
|
| 189 |
|
| 190 |
+
## π API Key Rotation *(Optional)*
|
| 191 |
+
|
| 192 |
+
Spread requests across multiple API keys to avoid rate limits. Supply a comma-separated pool for any provider β keys rotate round-robin per provider independently.
|
| 193 |
+
|
| 194 |
+
```bash
|
| 195 |
+
# Single provider, multiple keys
|
| 196 |
+
ANTHROPIC_API_KEYS=sk-ant-key1,sk-ant-key2,sk-ant-key3
|
| 197 |
+
|
| 198 |
+
# Multiple providers simultaneously
|
| 199 |
+
OPENAI_API_KEYS=sk-openai-key1,sk-openai-key2
|
| 200 |
+
GEMINI_API_KEYS=AIza-key1,AIza-key2
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
**Fallback chain** (per provider):
|
| 204 |
+
1. `{PROVIDER}_API_KEYS` β comma-separated pool *(preferred)*
|
| 205 |
+
2. `{PROVIDER}_API_KEY` β single dedicated key
|
| 206 |
+
3. `LLM_API_KEY` β universal fallback *(default for all providers)*
|
| 207 |
+
|
| 208 |
+
> [!TIP]
|
| 209 |
+
> If you only set `LLM_API_KEY`, all providers use it as a fallback automatically β no extra config needed. Add per-provider pools only when you need multi-key rotation.
|
| 210 |
+
|
| 211 |
+
Supported per-provider variables: `ANTHROPIC_API_KEYS`, `OPENAI_API_KEYS`, `GEMINI_API_KEYS`, `DEEPSEEK_API_KEYS`, `GROQ_API_KEYS`, `MISTRAL_API_KEYS`, `OPENROUTER_API_KEYS`, `XAI_API_KEYS`, `NVIDIA_API_KEYS`, `COHERE_API_KEYS`, `TOGETHER_API_KEYS`, `CEREBRAS_API_KEYS`, and more β see `.env.example` for the full list.
|
| 212 |
+
|
| 213 |
## π€ LLM Providers
|
| 214 |
|
| 215 |
HuggingClaw supports **all providers** from OpenClaw. Set `LLM_MODEL=<provider/model>` and the provider is auto-detected.
|
multi-provider-key-rotator.cjs
CHANGED
|
@@ -175,19 +175,36 @@ function normalizeKeys(...inputs) {
|
|
| 175 |
|
| 176 |
// Build per-provider key pools + rotation indices
|
| 177 |
const providerState = PROVIDERS.map(p => {
|
| 178 |
-
const
|
| 179 |
process.env[p.envPlural] || '',
|
| 180 |
process.env[p.envSingular] || '',
|
| 181 |
-
process.env.LLM_API_KEY || '',
|
| 182 |
);
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
| 186 |
console.log(`[key-rotator] ${p.name}: ${keys.length} key${keys.length === 1 ? '' : 's'}`);
|
|
|
|
|
|
|
| 187 |
}
|
|
|
|
| 188 |
return { ...p, keys, idx: 0 };
|
| 189 |
});
|
| 190 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
// βββ Runtime helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 192 |
|
| 193 |
function resolveHostname(urlLike) {
|
|
|
|
| 175 |
|
| 176 |
// Build per-provider key pools + rotation indices
|
| 177 |
const providerState = PROVIDERS.map(p => {
|
| 178 |
+
const dedicatedKeys = normalizeKeys(
|
| 179 |
process.env[p.envPlural] || '',
|
| 180 |
process.env[p.envSingular] || '',
|
|
|
|
| 181 |
);
|
| 182 |
+
const hasDedicated = dedicatedKeys.length > 0;
|
| 183 |
+
const keys = hasDedicated
|
| 184 |
+
? dedicatedKeys
|
| 185 |
+
: normalizeKeys(process.env.LLM_API_KEY || '');
|
| 186 |
+
|
| 187 |
+
if (hasDedicated) {
|
| 188 |
console.log(`[key-rotator] ${p.name}: ${keys.length} key${keys.length === 1 ? '' : 's'}`);
|
| 189 |
+
} else if (!keys.length) {
|
| 190 |
+
console.warn(`[key-rotator] No keys for provider "${p.name}"`);
|
| 191 |
}
|
| 192 |
+
|
| 193 |
return { ...p, keys, idx: 0 };
|
| 194 |
});
|
| 195 |
|
| 196 |
+
// Summarise providers that fall back to LLM_API_KEY
|
| 197 |
+
const fallbackCount = providerState.filter(p => {
|
| 198 |
+
const dedicated = normalizeKeys(
|
| 199 |
+
process.env[p.envPlural] || '',
|
| 200 |
+
process.env[p.envSingular] || '',
|
| 201 |
+
);
|
| 202 |
+
return dedicated.length === 0 && p.keys.length > 0;
|
| 203 |
+
}).length;
|
| 204 |
+
if (fallbackCount > 0) {
|
| 205 |
+
console.log(`[key-rotator] ${fallbackCount} provider(s) using LLM_API_KEY fallback`);
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
// βββ Runtime helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 209 |
|
| 210 |
function resolveHostname(urlLike) {
|