Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
# --- Chutes ---
#CHUTES_API_KEY_1="YOUR_CHUTES_API_KEY"

# --- MiniMax ---
#MINIMAX_API_KEY_1="YOUR_MINIMAX_API_KEY"

# ------------------------------------------------------------------------------
# | [OAUTH] Provider OAuth 2.0 Credentials |
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -86,6 +89,20 @@
# cannot automatically determine your Google Cloud Project ID.
#GEMINI_CLI_PROJECT_ID=""

# --- MiniMax Endpoint Selection ---
# Select the upstream region and compatibility protocol used for MiniMax calls.
# Supported regions: global_en, cn_zh
# Supported protocols: openai, anthropic
#MINIMAX_API_REGION="global_en"
#MINIMAX_API_PROTOCOL="openai"
#
# Anthropic-compatible base URLs must end with /anthropic. The client adapter
# appends /v1/messages when sending a Messages API request.
#MINIMAX_GLOBAL_OPENAI_BASE_URL="https://api.minimax.io/v1"
#MINIMAX_GLOBAL_ANTHROPIC_BASE_URL="https://api.minimax.io/anthropic"
#MINIMAX_CN_OPENAI_BASE_URL="https://api.minimaxi.com/v1"
#MINIMAX_CN_ANTHROPIC_BASE_URL="https://api.minimaxi.com/anthropic"

# --- Model Ignore Lists ---
# Specify a comma-separated list of model names to exclude from a provider's
# available models. This is useful for filtering out models you don't want to use.
Expand Down
25 changes: 24 additions & 1 deletion DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,30 @@ TIMEOUT_POOL=120

The library handles provider idiosyncrasies through specialized "Provider" classes in `src/rotator_library/providers/`.

### 3.1. Gemini CLI (`gemini_cli_provider.py`)
### 3.1. MiniMax (`minimax_provider.py`)

The MiniMax provider exposes the built-in `MiniMax-M3` and `MiniMax-M2.7`
models while preserving additional models returned by the provider's model
discovery endpoint. Native model metadata includes context limits, pricing,
input modalities, and thinking support for the `/v1/models` and cost APIs.

Configure the upstream region and protocol with these environment variables:

```env
MINIMAX_API_REGION="global_en" # or cn_zh
MINIMAX_API_PROTOCOL="openai" # or anthropic

MINIMAX_GLOBAL_OPENAI_BASE_URL="https://api.minimax.io/v1"
MINIMAX_GLOBAL_ANTHROPIC_BASE_URL="https://api.minimax.io/anthropic"
MINIMAX_CN_OPENAI_BASE_URL="https://api.minimaxi.com/v1"
MINIMAX_CN_ANTHROPIC_BASE_URL="https://api.minimaxi.com/anthropic"
```

Anthropic-compatible base URLs must end with `/anthropic`. The adapter passes
that base to the compatibility client, which appends `/v1/messages` for the
Messages API request.

### 3.2. Gemini CLI (`gemini_cli_provider.py`)

The `GeminiCliProvider` is the most complex implementation, mimicking the Google Cloud Code extension.

Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ openai/gpt-4o ← OpenAI API
anthropic/claude-3-5-sonnet ← Anthropic API
openrouter/anthropic/claude-3-opus ← OpenRouter
gemini_cli/gemini-2.5-pro ← Gemini CLI (OAuth)
minimax/MiniMax-M3 ← MiniMax OpenAI-compatible route
minimax/MiniMax-M2.7 ← MiniMax OpenAI-compatible route
```

### Usage Examples
Expand Down Expand Up @@ -278,6 +280,7 @@ GEMINI_API_KEY_1="your-gemini-key"
GEMINI_API_KEY_2="another-gemini-key"
OPENAI_API_KEY_1="your-openai-key"
ANTHROPIC_API_KEY_1="your-anthropic-key"
MINIMAX_API_KEY_1="your-minimax-key"
```

> Copy `.env.example` to `.env` as a starting point.
Expand Down Expand Up @@ -454,6 +457,25 @@ The proxy includes a powerful text-based UI for configuration and management.
| `IGNORE_MODELS_<PROVIDER>` | Blacklist (comma-separated, supports `*`) | `IGNORE_MODELS_OPENAI=*-preview*` |
| `WHITELIST_MODELS_<PROVIDER>` | Whitelist (overrides blacklist) | `WHITELIST_MODELS_GEMINI=gemini-2.5-pro` |

### MiniMax Endpoint Selection

MiniMax supports both configured regions and both compatibility protocols. Set
the region and protocol in `.env`; the provider plugin then routes requests
through the selected base URL.

```env
MINIMAX_API_REGION="global_en" # or cn_zh
MINIMAX_API_PROTOCOL="openai" # or anthropic

MINIMAX_GLOBAL_OPENAI_BASE_URL="https://api.minimax.io/v1"
MINIMAX_GLOBAL_ANTHROPIC_BASE_URL="https://api.minimax.io/anthropic"
MINIMAX_CN_OPENAI_BASE_URL="https://api.minimaxi.com/v1"
MINIMAX_CN_ANTHROPIC_BASE_URL="https://api.minimaxi.com/anthropic"
```

Anthropic-compatible base URLs must end with `/anthropic`. The adapter uses
the provider's `/v1/messages` request path without exposing a derived base URL.

### Advanced Features

| Variable | Description |
Expand Down
108 changes: 108 additions & 0 deletions src/rotator_library/minimax_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# SPDX-License-Identifier: LGPL-3.0-only
# Copyright (c) 2026 Mirrowel

"""MiniMax endpoint and model configuration."""

from __future__ import annotations

import os
from typing import Any, Dict, Optional


GLOBAL_EN = "global_en"
CN_ZH = "cn_zh"
OPENAI_PROTOCOL = "openai"
ANTHROPIC_PROTOCOL = "anthropic"

MINIMAX_ENDPOINTS: Dict[str, Dict[str, str]] = {
GLOBAL_EN: {
OPENAI_PROTOCOL: "https://api.minimax.io/v1",
ANTHROPIC_PROTOCOL: "https://api.minimax.io/anthropic",
},
CN_ZH: {
OPENAI_PROTOCOL: "https://api.minimaxi.com/v1",
ANTHROPIC_PROTOCOL: "https://api.minimaxi.com/anthropic",
},
}

MINIMAX_MODEL_DEFINITIONS: Dict[str, Dict[str, Any]] = {
"MiniMax-M3": {
"context_window": 1_000_000,
"pricing_usd_per_million_tokens": {
"input": 0.3,
"output": 1.2,
"cache_read": 0.06,
"cache_write": None,
},
"input_modalities": ["text", "image", "video"],
"thinking": ["adaptive", "disabled"],
},
"MiniMax-M2.7": {
"context_window": 204_800,
"pricing_usd_per_million_tokens": {
"input": 0.3,
"output": 1.2,
"cache_read": 0.06,
"cache_write": 0.375,
},
"input_modalities": ["text"],
"thinking": ["always_on"],
},
}

MINIMAX_DEFAULT_MODELS = tuple(MINIMAX_MODEL_DEFINITIONS)

_REGION_ENV_VARS = {
GLOBAL_EN: {
OPENAI_PROTOCOL: "MINIMAX_GLOBAL_OPENAI_BASE_URL",
ANTHROPIC_PROTOCOL: "MINIMAX_GLOBAL_ANTHROPIC_BASE_URL",
},
CN_ZH: {
OPENAI_PROTOCOL: "MINIMAX_CN_OPENAI_BASE_URL",
ANTHROPIC_PROTOCOL: "MINIMAX_CN_ANTHROPIC_BASE_URL",
},
}


def get_minimax_region() -> str:
"""Return the configured endpoint region, defaulting to the global service."""
region = os.getenv("MINIMAX_API_REGION", GLOBAL_EN).strip().lower()
return region if region in MINIMAX_ENDPOINTS else GLOBAL_EN


def get_minimax_protocol() -> str:
"""Return the configured upstream protocol."""
protocol = os.getenv("MINIMAX_API_PROTOCOL", OPENAI_PROTOCOL).strip().lower()
return (
protocol
if protocol in (OPENAI_PROTOCOL, ANTHROPIC_PROTOCOL)
else OPENAI_PROTOCOL
)


def get_minimax_endpoint(
region: Optional[str] = None,
protocol: Optional[str] = None,
) -> str:
"""Resolve a user-configured or default MiniMax endpoint."""
selected_region = region or get_minimax_region()
selected_protocol = protocol or get_minimax_protocol()

if selected_region not in MINIMAX_ENDPOINTS:
selected_region = GLOBAL_EN
if selected_protocol not in (OPENAI_PROTOCOL, ANTHROPIC_PROTOCOL):
selected_protocol = OPENAI_PROTOCOL

env_var = _REGION_ENV_VARS[selected_region][selected_protocol]
override = os.getenv(env_var, "").strip()
if not override:
if selected_protocol == OPENAI_PROTOCOL:
override = os.getenv("MINIMAX_API_BASE", "").strip()
else:
override = os.getenv("MINIMAX_ANTHROPIC_BASE_URL", "").strip()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINIMAX_ANTHROPIC_BASE_URL is read here but isn't documented anywhere (.env.example, README.md, DOCUMENTATION.md, or the provider_config.py extra_vars list), unlike its OpenAI counterpart MINIMAX_API_BASE. The naming is also asymmetric (MINIMAX_API_BASE vs MINIMAX_ANTHROPIC_BASE_URL).

Either document both fallback vars consistently, or drop them in favor of the four region/protocol-specific vars that are already documented.


endpoint = override or MINIMAX_ENDPOINTS[selected_region][selected_protocol]
endpoint = endpoint.rstrip("/")
if selected_protocol == ANTHROPIC_PROTOCOL and not endpoint.endswith("/anthropic"):
raise ValueError("MiniMax Anthropic base URL must end with /anthropic")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ValueError is effectively a no-op for users: transform_request runs inside a bare except Exception in transforms.py:114-115 that only logs at debug and continues. So a misconfigured Anthropic base URL never surfaces — instead the request falls through with half-mutated kwargs (model already rewritten to anthropic/... but no api_base/custom_llm_provider), then fails opaquely at the LiteLLM layer.

Consider validating at config/startup time, or having the provider log a warning and fall back to a safe default rather than raising into a swallowed hook.

return endpoint
56 changes: 56 additions & 0 deletions src/rotator_library/model_info_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from urllib.request import Request, urlopen
from urllib.error import URLError

from .minimax_config import MINIMAX_MODEL_DEFINITIONS

logger = logging.getLogger(__name__)


Expand All @@ -45,6 +47,7 @@
"meta-llama",
"nvidia",
"moonshotai", # Used in nvidia_nim/moonshotai/model format
"minimax",
# These are aggregators/proxies - lower priority
"openrouter",
"azure",
Expand Down Expand Up @@ -86,6 +89,46 @@
}


def _build_minimax_model_catalog() -> Dict[str, Dict[str, Any]]:
"""Build native metadata records for the supported MiniMax models."""
catalog = {}
for model_id, definition in MINIMAX_MODEL_DEFINITIONS.items():
pricing = definition["pricing_usd_per_million_tokens"]
input_types = definition["input_modalities"]
catalog[f"minimax/{model_id}"] = {
"name": model_id,
"original_id": model_id,
"provider": "minimax",
"source": "native",
"category": "chat",
"prompt_cost": pricing["input"] / 1_000_000,
"completion_cost": pricing["output"] / 1_000_000,
"cache_read_cost": pricing["cache_read"] / 1_000_000,
"cache_write_cost": (
pricing["cache_write"] / 1_000_000
if pricing["cache_write"] is not None
else None
),
"context": definition["context_window"],
"max_out": 0,
"inputs": input_types,
"outputs": ["text"],
"has_tools": False,
"has_functions": False,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_tools/has_functions are hardcoded False for both models and surfaced via the /v1/models capability metadata. MiniMax-M3 (and M2.7) advertise function/tool calling in their public docs — if so, this understates their capabilities to clients querying the proxy. Are these intentionally False, or should at least M3 report tool support?

"has_reasoning": bool(definition["thinking"]),
"has_vision": "image" in input_types,
"has_structured_output": False,
"has_temperature": True,
"has_attachments": "video" in input_types,
"has_interleaved": False,
"supported_parameters": ["thinking"],
}
return catalog


MINIMAX_MODEL_CATALOG = _build_minimax_model_catalog()


def _get_provider_priority(provider: str) -> int:
"""
Get priority score for a provider (lower = better).
Expand Down Expand Up @@ -922,6 +965,7 @@ def __init__(
# Raw data stores
self._openrouter_store: Dict[str, Dict] = {}
self._modelsdev_store: Dict[str, Dict] = {}
self._native_store: Dict[str, Dict] = MINIMAX_MODEL_CATALOG.copy()

# Lookup infrastructure
self._index = ModelIndex()
Expand Down Expand Up @@ -1022,6 +1066,9 @@ def _rebuild_index(self):
for model_id in self._modelsdev_store:
self._index.add(model_id)

for model_id in self._native_store:
self._index.add(model_id)

# ---------- Query API ----------

def lookup(self, model_id: str) -> Optional[ModelMetadata]:
Expand Down Expand Up @@ -1064,6 +1111,13 @@ def _resolve_model(self, model_id: str) -> Optional[ModelMetadata]:
)
quality = "exact"

if model_id in self._native_store:
records.insert(
0,
(self._native_store[model_id], f"native:exact:{model_id}"),
)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
quality = "exact"

# Step 2: Try provider alias substitution for direct match
# This handles cases like nvidia_nim/org/model -> nvidia/org/model
if not records:
Expand Down Expand Up @@ -1215,6 +1269,7 @@ def all_raw_models(self) -> Dict[str, Dict]:
combined = {}
combined.update(self._openrouter_store)
combined.update(self._modelsdev_store)
combined.update(self._native_store)
return combined

def diagnostics(self) -> Dict[str, Any]:
Expand All @@ -1224,6 +1279,7 @@ def diagnostics(self) -> Dict[str, Any]:
"last_refresh": self._last_refresh,
"openrouter_count": len(self._openrouter_store),
"modelsdev_count": len(self._modelsdev_store),
"native_count": len(self._native_store),
"cached_lookups": len(self._result_cache),
"index_entries": self._index.entry_count(),
"refresh_interval": self._refresh_interval,
Expand Down
39 changes: 38 additions & 1 deletion src/rotator_library/provider_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
get_provider_api_key_var,
get_provider_display_name,
)
from .minimax_config import (
ANTHROPIC_PROTOCOL,
CN_ZH,
GLOBAL_EN,
MINIMAX_ENDPOINTS,
OPENAI_PROTOCOL,
)

lib_logger = logging.getLogger("rotator_library")

Expand Down Expand Up @@ -93,8 +100,38 @@
},
"minimax": {
"category": "popular",
"note": (
"Set MINIMAX_API_REGION to global_en or cn_zh and "
"MINIMAX_API_PROTOCOL to openai or anthropic."
),
"extra_vars": [
("MINIMAX_API_BASE", "API Base URL (optional)", None),
("MINIMAX_API_REGION", "Endpoint region", GLOBAL_EN),
("MINIMAX_API_PROTOCOL", "Upstream protocol", OPENAI_PROTOCOL),
(
"MINIMAX_API_BASE",
"OpenAI-compatible base override (optional)",
None,
),
(
"MINIMAX_GLOBAL_OPENAI_BASE_URL",
"Global OpenAI-compatible base URL",
MINIMAX_ENDPOINTS[GLOBAL_EN][OPENAI_PROTOCOL],
),
(
"MINIMAX_GLOBAL_ANTHROPIC_BASE_URL",
"Global Anthropic-compatible base URL",
MINIMAX_ENDPOINTS[GLOBAL_EN][ANTHROPIC_PROTOCOL],
),
(
"MINIMAX_CN_OPENAI_BASE_URL",
"China OpenAI-compatible base URL",
MINIMAX_ENDPOINTS[CN_ZH][OPENAI_PROTOCOL],
),
(
"MINIMAX_CN_ANTHROPIC_BASE_URL",
"China Anthropic-compatible base URL",
MINIMAX_ENDPOINTS[CN_ZH][ANTHROPIC_PROTOCOL],
),
],
},
"xiaomi_mimo": {
Expand Down
Loading
Loading