Spaces:
Running
Running
feat: add support for registering custom OpenAI-compatible providers via environment variables
Browse files- .env.example +24 -0
- README.md +44 -0
- start.sh +67 -0
.env.example
CHANGED
|
@@ -124,6 +124,30 @@ LLM_API_KEY=your_api_key_here
|
|
| 124 |
# Or any other OpenClaw-supported provider (format: provider/model-name)
|
| 125 |
LLM_MODEL=anthropic/claude-sonnet-4-5
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
# [REQUIRED] Gateway authentication token
|
| 128 |
# Generate: openssl rand -hex 32
|
| 129 |
GATEWAY_TOKEN=your_gateway_token_here
|
|
|
|
| 124 |
# Or any other OpenClaw-supported provider (format: provider/model-name)
|
| 125 |
LLM_MODEL=anthropic/claude-sonnet-4-5
|
| 126 |
|
| 127 |
+
# Optional: custom OpenAI-compatible provider
|
| 128 |
+
# Only use this if you want to register your own endpoint at startup.
|
| 129 |
+
# Leave all of these empty unless you need a custom provider.
|
| 130 |
+
#
|
| 131 |
+
# Example:
|
| 132 |
+
# CUSTOM_PROVIDER_NAME=modal
|
| 133 |
+
# CUSTOM_BASE_URL=https://api.us-west-2.modal.direct/v1
|
| 134 |
+
# CUSTOM_MODEL_ID=zai-org/GLM-5.1-FP8
|
| 135 |
+
# LLM_MODEL=modal/zai-org/GLM-5.1-FP8
|
| 136 |
+
#
|
| 137 |
+
# Notes:
|
| 138 |
+
# - CUSTOM_BASE_URL should be the API base URL, not /chat/completions
|
| 139 |
+
# - CUSTOM_PROVIDER_NAME must not reuse a built-in provider name
|
| 140 |
+
# - Uses LLM_API_KEY by default; set CUSTOM_API_KEY only if different
|
| 141 |
+
#
|
| 142 |
+
# CUSTOM_PROVIDER_NAME=modal
|
| 143 |
+
# CUSTOM_BASE_URL=https://your-openai-compatible-endpoint/v1
|
| 144 |
+
# CUSTOM_MODEL_ID=your-model-id
|
| 145 |
+
# CUSTOM_MODEL_NAME=Friendly Model Name
|
| 146 |
+
# CUSTOM_API_KEY=your_custom_api_key_here
|
| 147 |
+
# CUSTOM_API_TYPE=openai-completions
|
| 148 |
+
# CUSTOM_CONTEXT_WINDOW=128000
|
| 149 |
+
# CUSTOM_MAX_TOKENS=500
|
| 150 |
+
|
| 151 |
# [REQUIRED] Gateway authentication token
|
| 152 |
# Generate: openssl rand -hex 32
|
| 153 |
GATEWAY_TOKEN=your_gateway_token_here
|
README.md
CHANGED
|
@@ -219,6 +219,50 @@ LLM_MODEL=provider/model-name
|
|
| 219 |
|
| 220 |
The provider prefix in `LLM_MODEL` tells HuggingClaw how to call it. See [OpenClaw Model Providers](https://docs.openclaw.ai/concepts/model-providers) for the full list.
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
## 💻 Local Development
|
| 223 |
|
| 224 |
```bash
|
|
|
|
| 219 |
|
| 220 |
The provider prefix in `LLM_MODEL` tells HuggingClaw how to call it. See [OpenClaw Model Providers](https://docs.openclaw.ai/concepts/model-providers) for the full list.
|
| 221 |
|
| 222 |
+
### Custom OpenAI-Compatible Provider
|
| 223 |
+
|
| 224 |
+
You can register your own OpenAI-compatible endpoint at startup without touching OpenClaw CLI.
|
| 225 |
+
|
| 226 |
+
Required env vars:
|
| 227 |
+
|
| 228 |
+
```bash
|
| 229 |
+
LLM_API_KEY=your_api_key
|
| 230 |
+
LLM_MODEL=modal/zai-org/GLM-5.1-FP8
|
| 231 |
+
CUSTOM_PROVIDER_NAME=modal
|
| 232 |
+
CUSTOM_BASE_URL=https://api.us-west-2.modal.direct/v1
|
| 233 |
+
CUSTOM_MODEL_ID=zai-org/GLM-5.1-FP8
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
Optional env vars:
|
| 237 |
+
|
| 238 |
+
```bash
|
| 239 |
+
CUSTOM_MODEL_NAME=GLM-5.1-FP8
|
| 240 |
+
CUSTOM_API_KEY=your_custom_api_key
|
| 241 |
+
CUSTOM_API_TYPE=openai-completions
|
| 242 |
+
CUSTOM_CONTEXT_WINDOW=128000
|
| 243 |
+
CUSTOM_MAX_TOKENS=500
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
Notes:
|
| 247 |
+
|
| 248 |
+
- This is for **OpenAI-compatible** endpoints only.
|
| 249 |
+
- `CUSTOM_BASE_URL` should be the API base URL, not `/chat/completions`.
|
| 250 |
+
- `CUSTOM_PROVIDER_NAME` must be a new name and cannot override built-in providers like `openai`, `openrouter`, `google`, or `anthropic`.
|
| 251 |
+
- If `CUSTOM_API_KEY` is not set, HuggingClaw uses `LLM_API_KEY`.
|
| 252 |
+
|
| 253 |
+
Examples:
|
| 254 |
+
|
| 255 |
+
- Modal-hosted model:
|
| 256 |
+
- `CUSTOM_PROVIDER_NAME=modal`
|
| 257 |
+
- `CUSTOM_BASE_URL=https://api.us-west-2.modal.direct/v1`
|
| 258 |
+
- `CUSTOM_MODEL_ID=zai-org/GLM-5.1-FP8`
|
| 259 |
+
- `LLM_MODEL=modal/zai-org/GLM-5.1-FP8`
|
| 260 |
+
- Self-hosted vLLM:
|
| 261 |
+
- `CUSTOM_PROVIDER_NAME=vllm`
|
| 262 |
+
- `CUSTOM_BASE_URL=https://your-vllm.example.com/v1`
|
| 263 |
+
- `CUSTOM_MODEL_ID=Qwen/Qwen2.5-72B-Instruct`
|
| 264 |
+
- `LLM_MODEL=vllm/Qwen/Qwen2.5-72B-Instruct`
|
| 265 |
+
|
| 266 |
## 💻 Local Development
|
| 267 |
|
| 268 |
```bash
|
start.sh
CHANGED
|
@@ -231,6 +231,73 @@ CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".gateway.auth.token = \"$GATEWAY_TOKEN\"
|
|
| 231 |
# Model configuration at top level
|
| 232 |
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".agents.defaults.model = \"$LLM_MODEL\"")
|
| 233 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
# Browser configuration (managed local Chromium in HF/Docker)
|
| 235 |
BROWSER_EXECUTABLE_PATH=""
|
| 236 |
for candidate in /usr/bin/chromium /usr/bin/chromium-browser /snap/bin/chromium; do
|
|
|
|
| 231 |
# Model configuration at top level
|
| 232 |
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".agents.defaults.model = \"$LLM_MODEL\"")
|
| 233 |
|
| 234 |
+
# Optional: dynamic custom OpenAI-compatible provider registration
|
| 235 |
+
CUSTOM_PROVIDER_NAME="${CUSTOM_PROVIDER_NAME:-}"
|
| 236 |
+
CUSTOM_BASE_URL="${CUSTOM_BASE_URL:-}"
|
| 237 |
+
CUSTOM_MODEL_ID="${CUSTOM_MODEL_ID:-}"
|
| 238 |
+
CUSTOM_MODEL_NAME="${CUSTOM_MODEL_NAME:-$CUSTOM_MODEL_ID}"
|
| 239 |
+
CUSTOM_API_KEY="${CUSTOM_API_KEY:-$LLM_API_KEY}"
|
| 240 |
+
CUSTOM_API_TYPE="${CUSTOM_API_TYPE:-openai-completions}"
|
| 241 |
+
CUSTOM_CONTEXT_WINDOW="${CUSTOM_CONTEXT_WINDOW:-128000}"
|
| 242 |
+
CUSTOM_MAX_TOKENS="${CUSTOM_MAX_TOKENS:-500}"
|
| 243 |
+
|
| 244 |
+
if [ -n "$CUSTOM_PROVIDER_NAME" ] || [ -n "$CUSTOM_BASE_URL" ] || [ -n "$CUSTOM_MODEL_ID" ]; then
|
| 245 |
+
CUSTOM_PROVIDER_NORMALIZED=$(printf '%s' "$CUSTOM_PROVIDER_NAME" | tr '[:upper:]' '[:lower:]')
|
| 246 |
+
CUSTOM_BASE_URL_NORMALIZED="${CUSTOM_BASE_URL%/}"
|
| 247 |
+
CUSTOM_PROVIDER_OK=true
|
| 248 |
+
|
| 249 |
+
if [ -z "$CUSTOM_PROVIDER_NAME" ] || [ -z "$CUSTOM_BASE_URL" ] || [ -z "$CUSTOM_MODEL_ID" ]; then
|
| 250 |
+
echo "⚠️ Custom provider skipped: set CUSTOM_PROVIDER_NAME, CUSTOM_BASE_URL, and CUSTOM_MODEL_ID together."
|
| 251 |
+
CUSTOM_PROVIDER_OK=false
|
| 252 |
+
fi
|
| 253 |
+
|
| 254 |
+
case "$CUSTOM_PROVIDER_NORMALIZED" in
|
| 255 |
+
anthropic|openai|openai-codex|google|google-vertex|deepseek|opencode|opencode-go|openrouter|kilocode|vercel-ai-gateway|zai|z-ai|z.ai|zhipu|moonshot|kimi-coding|minimax|qwen|modelstudio|xiaomi|volcengine|volcengine-plan|byteplus|byteplus-plan|qianfan|mistral|mistralai|xai|x-ai|nvidia|cohere|groq|together|huggingface|cerebras|venice|synthetic|github-copilot)
|
| 256 |
+
echo "⚠️ Custom provider skipped: CUSTOM_PROVIDER_NAME='$CUSTOM_PROVIDER_NAME' conflicts with a built-in provider."
|
| 257 |
+
CUSTOM_PROVIDER_OK=false
|
| 258 |
+
;;
|
| 259 |
+
esac
|
| 260 |
+
|
| 261 |
+
if [[ "$CUSTOM_BASE_URL_NORMALIZED" == */chat/completions ]] || [[ "$CUSTOM_BASE_URL_NORMALIZED" == */completions ]]; then
|
| 262 |
+
echo "⚠️ Custom provider skipped: CUSTOM_BASE_URL should be the API base URL, not a completions endpoint."
|
| 263 |
+
CUSTOM_PROVIDER_OK=false
|
| 264 |
+
fi
|
| 265 |
+
|
| 266 |
+
if ! [[ "$CUSTOM_CONTEXT_WINDOW" =~ ^[0-9]+$ ]] || ! [[ "$CUSTOM_MAX_TOKENS" =~ ^[0-9]+$ ]]; then
|
| 267 |
+
echo "⚠️ Custom provider skipped: CUSTOM_CONTEXT_WINDOW and CUSTOM_MAX_TOKENS must be whole numbers."
|
| 268 |
+
CUSTOM_PROVIDER_OK=false
|
| 269 |
+
fi
|
| 270 |
+
|
| 271 |
+
if [ "$CUSTOM_PROVIDER_OK" = "true" ]; then
|
| 272 |
+
echo "🔧 Registering custom provider: $CUSTOM_PROVIDER_NAME → $CUSTOM_BASE_URL_NORMALIZED"
|
| 273 |
+
CONFIG_JSON=$(jq \
|
| 274 |
+
--arg provider "$CUSTOM_PROVIDER_NAME" \
|
| 275 |
+
--arg baseUrl "$CUSTOM_BASE_URL_NORMALIZED" \
|
| 276 |
+
--arg apiKey "$CUSTOM_API_KEY" \
|
| 277 |
+
--arg apiType "$CUSTOM_API_TYPE" \
|
| 278 |
+
--arg modelId "$CUSTOM_MODEL_ID" \
|
| 279 |
+
--arg modelName "$CUSTOM_MODEL_NAME" \
|
| 280 |
+
--argjson contextWindow "$CUSTOM_CONTEXT_WINDOW" \
|
| 281 |
+
--argjson maxTokens "$CUSTOM_MAX_TOKENS" \
|
| 282 |
+
'.models.mode = "merge" |
|
| 283 |
+
.models.providers[$provider] = {
|
| 284 |
+
"baseUrl": $baseUrl,
|
| 285 |
+
"apiKey": $apiKey,
|
| 286 |
+
"api": $apiType,
|
| 287 |
+
"models": [{
|
| 288 |
+
"id": $modelId,
|
| 289 |
+
"name": $modelName,
|
| 290 |
+
"contextWindow": $contextWindow,
|
| 291 |
+
"maxTokens": $maxTokens
|
| 292 |
+
}]
|
| 293 |
+
}' <<<"$CONFIG_JSON")
|
| 294 |
+
|
| 295 |
+
if [[ "$LLM_MODEL" != "$CUSTOM_PROVIDER_NAME/"* ]]; then
|
| 296 |
+
echo "⚠️ Custom provider registered, but LLM_MODEL='$LLM_MODEL' does not start with '$CUSTOM_PROVIDER_NAME/'."
|
| 297 |
+
fi
|
| 298 |
+
fi
|
| 299 |
+
fi
|
| 300 |
+
|
| 301 |
# Browser configuration (managed local Chromium in HF/Docker)
|
| 302 |
BROWSER_EXECUTABLE_PATH=""
|
| 303 |
for candidate in /usr/bin/chromium /usr/bin/chromium-browser /snap/bin/chromium; do
|