# Custom OpenAI-Compatible Providers in Hermes

When a provider isn't built-in but offers an OpenAI-compatible `/v1` endpoint, add it manually to `config.yaml`.

## Steps

1. **Find the base URL and API key env var** — usually `/v1` at the provider's domain.

2. **List available models:**
   ```bash
   curl -s "https://<provider>/v1/models" \
     -H "Authorization: Bearer $API_KEY" \
     --max-time 15 | python3 -m json.tool
   ```
   Note model `id`, `input_modalities`, `context_length`, `supported_features`.

3. **Add provider to config.yaml via Python** (don't hand-edit YAML — indentation errors break everything):
   ```python
   import yaml, json, os

   config_path = os.path.expanduser("~/.hermes/config.yaml")
   with open(config_path) as f:
       config = yaml.safe_load(f)

   # Read API key from .env
   api_key = ""
   with open(os.path.expanduser("~/.hermes/.env")) as f:
       for line in f:
           if line.startswith("YOUR_KEY_PREFIX="):
               api_key = line.strip().split("=", 1)[1]
               break

   config.setdefault("providers", {})["your-provider"] = {
       "api_key": api_key,
       "base_url": "https://provider.example.com/v1",
       "available_models_json": json.dumps([
           {"id": "model-id", "name": "Display Name"},
       ]),
       "model": "default-model-id",
       "model_display_name": "Default Display Name"
   }

   with open(config_path, "w") as f:
       yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
   ```

4. **Verify with a test call:**
   ```bash
   curl -s "https://provider.example.com/v1/chat/completions" \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"model":"model-id","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'
   ```

5. **Use the model:** `hermes -m your-provider/model-id` or via `hermes model` picker.

## Provider Config Fields

| Field | Required | Notes |
|-------|----------|-------|
| `api_key` | Yes | Actual key value, not env var reference |
| `base_url` | Yes | Must end with `/v1` (or `/v1/`) |
| `available_models_json` | Yes | JSON string of `[{id, name}]` array |
| `model` | No | Default model ID |
| `model_display_name` | No | Human-readable default model name |
| `api_mode` | No | Only set if non-standard (e.g. `anthropic-messages` for MiniMax). Omit for OpenAI-compatible. |
| `protocol` | No | Usually leave as `''` |

## Pitfalls

- **Don't hand-edit YAML** — use Python `yaml.safe_load` + `yaml.dump` to avoid indentation corruption.
- **`api_key` must be the actual value**, not `$ENV_VAR` — Hermes doesn't resolve env vars inside provider config (only in `.env`).
- **No `api_mode` needed for OpenAI-compatible** — only set this for providers with custom protocols (Anthropic Messages, etc.).
- **`reasoning` field in responses** — some providers (SenseNova, DeepSeek) return a `reasoning` field in the message object. Hermes handles this natively for reasoning-capable models.
- **Model discovery** — always call `/v1/models` first; don't guess model IDs from documentation (they change).

## Known Custom Providers

| Provider | Base URL | Key Env Var | Models |
|----------|----------|-------------|--------|
| SenseNova | `https://token.sensenova.cn/v1` | `SN_API_KEY` | sensenova-6.7-flash-lite, deepseek-v4-flash, sensenova-u1-fast |
