Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
88 changes: 88 additions & 0 deletions providers/model_catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,94 @@
"open_weights": true
}
]
},
"minimax": {
"latest_planning": "MiniMax-M3",
"latest_development": "MiniMax-M3",
"latest_fast": "MiniMax-M3",
"tier_fallback": {
"development": "planning",
"fast": "planning"
},
"notes": "MiniMax-M3 is used for all tiers; MiniMax-M2.7 (tier: legacy) is catalog-only and not a latest_* default.",
"endpoints": [
{
"region": "global_en",
"openai_base_url": "https://api.minimax.io/v1",
"anthropic_base_url": "https://api.minimax.io/anthropic",
"docs_root": "https://platform.minimax.io/docs"
},
{
"region": "cn_zh",
"openai_base_url": "https://api.minimaxi.com/v1",
"anthropic_base_url": "https://api.minimaxi.com/anthropic",
"docs_root": "https://platform.minimaxi.com/docs"
}
],
"models": [
{
"id": "MiniMax-M3",
"tier": "planning",
"context_window": 1000000,
"max_output": 524288,
"input_modalities": ["text", "image", "video"],
"thinking": ["adaptive", "disabled"],
"pricing_usd_per_million_tokens": {
"input": 0.6,
"output": 2.4,
"cache_read": 0.12,
"cache_write": null
},
"pricing_tiers_usd_per_million_tokens": [
{
"service_tier": "standard",
"input_tokens_lte": 512000,
"input": 0.3,
"output": 1.2,
"cache_read": 0.06,
"cache_write": null
},
{
"service_tier": "standard",
"input_tokens_gt": 512000,
"input": 0.6,
"output": 2.4,
"cache_read": 0.12,
"cache_write": null
},
{
"service_tier": "priority",
"input_tokens_lte": 512000,
"input": 0.45,
"output": 1.8,
"cache_read": 0.09,
"cache_write": null
},
{
"service_tier": "priority",
"input_tokens_gt": 512000,
"input": 0.9,
"output": 3.6,
"cache_read": 0.18,
"cache_write": null
}
]
},
{
"id": "MiniMax-M2.7",
"tier": "legacy",
"context_window": 204800,
"max_output": 204800,
"input_modalities": ["text"],
"thinking": ["always_on"],
"pricing_usd_per_million_tokens": {
"input": 0.3,
"output": 1.2,
"cache_read": 0.06,
"cache_write": 0.375
}
}
]
}
}
}
3 changes: 3 additions & 0 deletions tests/run-all-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,9 @@ run_test "Emit JSON Escape (C0 control chars + UTF-8)" "$SCRIPT_DIR/test-emit-js
# against CODEX_KNOWN_MODELS. Regression guard for the silent-downgrade bug.
run_test "Codex Model Trusted (LOKI_CODEX_MODEL verbatim)" "$SCRIPT_DIR/test-codex-model-trusted.sh"

# MiniMax catalog metadata and user-configurable Anthropic/OpenAI adapter paths.
run_test "MiniMax Model Catalog and Compatible Endpoints" "$SCRIPT_DIR/test-minimax-model-catalog.sh"

# provider_invoke()/provider_invoke_with_tier() argv construction across all
# four providers. Registered 2026-07-27: this file existed but was wired into
# NO runner, so the layer it guards went unwatched -- which is how a hardcoded
Expand Down
161 changes: 161 additions & 0 deletions tests/test-minimax-model-catalog.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env bash
# Validate MiniMax model metadata and the existing compatible adapter paths.

set -u

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CATALOG="$REPO_ROOT/providers/model_catalog.json"
PROVIDER_DOCS="$REPO_ROOT/wiki/Providers.md"

PASS=0
FAIL=0

ok() { printf 'PASS: %s\n' "$1"; PASS=$((PASS + 1)); }
bad() { printf 'FAIL: %s\n' "$1"; FAIL=$((FAIL + 1)); }

if python3 - "$CATALOG" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as handle:
catalog = json.load(handle)

provider = catalog["providers"]["minimax"]
assert provider["latest_planning"] == "MiniMax-M3"
assert provider["latest_development"] == "MiniMax-M3"
assert provider["latest_fast"] == "MiniMax-M3"

endpoints = {item["region"]: item for item in provider["endpoints"]}
assert endpoints == {
"global_en": {
"region": "global_en",
"openai_base_url": "https://api.minimax.io/v1",
"anthropic_base_url": "https://api.minimax.io/anthropic",
"docs_root": "https://platform.minimax.io/docs",
},
"cn_zh": {
"region": "cn_zh",
"openai_base_url": "https://api.minimaxi.com/v1",
"anthropic_base_url": "https://api.minimaxi.com/anthropic",
"docs_root": "https://platform.minimaxi.com/docs",
},
}
assert all(item["anthropic_base_url"].endswith("/anthropic") for item in endpoints.values())

models = {item["id"]: item for item in provider["models"]}
assert list(models)[:2] == ["MiniMax-M3", "MiniMax-M2.7"]

m3 = models["MiniMax-M3"]
assert m3["context_window"] == 1_000_000
assert m3["max_output"] == 524_288
assert m3["input_modalities"] == ["text", "image", "video"]
assert m3["thinking"] == ["adaptive", "disabled"]
assert m3["pricing_usd_per_million_tokens"] == {
"input": 0.6,
"output": 2.4,
"cache_read": 0.12,
"cache_write": None,
}
assert m3["pricing_tiers_usd_per_million_tokens"] == [
{
"service_tier": "standard",
"input_tokens_lte": 512_000,
"input": 0.3,
"output": 1.2,
"cache_read": 0.06,
"cache_write": None,
},
{
"service_tier": "standard",
"input_tokens_gt": 512_000,
"input": 0.6,
"output": 2.4,
"cache_read": 0.12,
"cache_write": None,
},
{
"service_tier": "priority",
"input_tokens_lte": 512_000,
"input": 0.45,
"output": 1.8,
"cache_read": 0.09,
"cache_write": None,
},
{
"service_tier": "priority",
"input_tokens_gt": 512_000,
"input": 0.9,
"output": 3.6,
"cache_read": 0.18,
"cache_write": None,
},
]

m27 = models["MiniMax-M2.7"]
assert m27["context_window"] == 204_800
assert m27["max_output"] == 204_800
assert m27["input_modalities"] == ["text"]
assert m27["thinking"] == ["always_on"]
assert m27["pricing_usd_per_million_tokens"] == {
"input": 0.3,
"output": 1.2,
"cache_read": 0.06,
"cache_write": 0.375,
}
PY
then
ok "catalog preserves model, endpoint, context, capability, and pricing metadata"
else
bad "catalog metadata does not match the supported MiniMax configuration"
fi

docs_ok=true
for text in \
'https://api.minimax.io/anthropic' \
'https://api.minimaxi.com/anthropic' \
'https://api.minimax.io/v1' \
'https://api.minimaxi.com/v1' \
'ANTHROPIC_BASE_URL' \
'ANTHROPIC_AUTH_TOKEN' \
'LOKI_MODEL_OVERRIDE' \
'OPENAI_API_BASE' \
'OPENAI_API_KEY' \
'LOKI_AIDER_MODEL' \
'MiniMax-M3' \
'MiniMax-M2.7'; do
grep -Fq "$text" "$PROVIDER_DOCS" || docs_ok=false
done
if [ "$docs_ok" = true ]; then
ok "provider documentation covers both regions, protocols, and models"
else
bad "provider documentation is missing required MiniMax configuration"
fi

anthropic_model="$({
env -i HOME="$HOME" PATH="$PATH" \
ANTHROPIC_BASE_URL="https://api.minimax.io/anthropic" \
LOKI_MODEL_OVERRIDE="MiniMax-M3" \
bash -c 'cd "$1"; . providers/claude.sh; resolve_model_for_tier development' _ "$REPO_ROOT"
} 2>/dev/null)"
if [ "$anthropic_model" = "MiniMax-M3" ]; then
ok "Anthropic-compatible adapter resolves the configured model"
else
bad "Anthropic-compatible adapter resolved '$anthropic_model'"
fi

aider_model="$({
env -i HOME="$HOME" PATH="$PATH" \
OPENAI_API_BASE="https://api.minimax.io/v1" \
OPENAI_API_KEY="test-key" \
LOKI_AIDER_MODEL="openai/MiniMax-M2.7" \
bash -c 'cd "$1"; . providers/aider.sh; printf "%s" "$AIDER_DEFAULT_MODEL"' _ "$REPO_ROOT"
} 2>/dev/null)"
if [ "$aider_model" = "openai/MiniMax-M2.7" ]; then
ok "OpenAI-compatible adapter resolves the configured model"
else
bad "OpenAI-compatible adapter resolved '$aider_model'"
fi

printf '\nTotal: %s Passed: %s Failed: %s\n' "$((PASS + FAIL))" "$PASS" "$FAIL"
[ "$FAIL" -eq 0 ]
42 changes: 42 additions & 0 deletions wiki/Providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,48 @@ Loki Mode supports four active AI providers with different capability levels, pl

---

## MiniMax API compatibility

The `minimax` model catalog entry records API and model metadata. It is not a fifth value for `--provider`; use an existing CLI adapter with the compatible API protocol.

### Anthropic-compatible API

Choose the Base URL for the account region. Keep `/anthropic` as the end of the Base URL; the client appends `/v1/messages` when it sends a request.

| Region | Base URL |
|--------|----------|
| Global | `https://api.minimax.io/anthropic` |
| China | `https://api.minimaxi.com/anthropic` |

```bash
export ANTHROPIC_BASE_URL="https://api.minimax.io/anthropic"
export ANTHROPIC_AUTH_TOKEN="<MINIMAX_API_KEY>"
export LOKI_MODEL_OVERRIDE="MiniMax-M3"
loki start ./prd.md --provider claude
```

Set `ANTHROPIC_BASE_URL` to the China Base URL when required. Change `LOKI_MODEL_OVERRIDE` to `MiniMax-M2.7` to use that model.

### OpenAI-compatible API

Choose the Base URL for the account region. Keep `/v1` as the end of the Base URL; the client appends `/chat/completions` when it sends a request.

| Region | Base URL |
|--------|----------|
| Global | `https://api.minimax.io/v1` |
| China | `https://api.minimaxi.com/v1` |

```bash
export OPENAI_API_BASE="https://api.minimax.io/v1"
export OPENAI_API_KEY="<MINIMAX_API_KEY>"
export LOKI_AIDER_MODEL="openai/MiniMax-M3"
loki start ./prd.md --provider aider
```

Set `OPENAI_API_BASE` to the China Base URL when required. Change the model suffix to `MiniMax-M2.7` to use that model.

---

## Provider precedence (v7.7.2)

When multiple sources specify a provider, Loki picks the first match in this order (highest wins):
Expand Down
Loading