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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ ANTHROPIC_API_KEY=
# Google Gemini — set this if using Google as your primary provider.
GOOGLE_API_KEY=

# MiniMax — set this if using MiniMax as your primary provider.
# Get your key at https://platform.minimaxi.com
MINIMAX_API_KEY=


# ── Model selection ───────────────────────────

# Override the default model for all agents (set automatically by onboarding).
# OpenAI example: DEFAULT_MODEL=gpt-5.2
# Anthropic example: DEFAULT_MODEL=litellm/claude-sonnet-4-6
# Google example: DEFAULT_MODEL=litellm/gemini/gemini-3-flash
# MiniMax example: DEFAULT_MODEL=minimax/MiniMax-M2.7
DEFAULT_MODEL=


Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ The setup wizard walks you through everything, but you'll need at least one of t

- `OPENAI_API_KEY` - For GPT 5.5 and Sora video generation
- `ANTHROPIC_API_KEY` - For Claude models
- `MINIMAX_API_KEY` - For MiniMax models (MiniMax-M2.7)

**Optional superpowers:**

Expand Down
6 changes: 6 additions & 0 deletions onboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
"default_model": "litellm/gemini/gemini-3-flash",
"url": "https://aistudio.google.com/app/apikey",
},
{
"name": "MiniMax",
"env_key": "MINIMAX_API_KEY",
"default_model": "minimax/MiniMax-M2.7",
"url": "https://platform.minimaxi.com",
},
]

# ── add-on definitions ────────────────────────────────────────────────────────
Expand Down
Empty file added tests/__init__.py
Empty file.
74 changes: 74 additions & 0 deletions tests/test_minimax_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Integration test for MiniMax provider via LiteLLM OpenAI-compatible API.

Requires MINIMAX_API_KEY to be set. Skipped automatically when the key is absent.
"""

from __future__ import annotations

import os

import pytest

# Load API key from ~/.env.local if present
try:
from dotenv import load_dotenv
from pathlib import Path
_env_local = Path.home() / ".env.local"
if _env_local.exists():
load_dotenv(_env_local)
except ImportError:
pass

_API_KEY = os.getenv("MINIMAX_API_KEY")

pytestmark = pytest.mark.skipif(not _API_KEY, reason="MINIMAX_API_KEY not set")


@pytest.mark.timeout(30)
def test_minimax_chat_completion():
"""Send a basic chat message via the MiniMax OpenAI-compatible API."""
import httpx

response = httpx.post(
"https://api.minimax.io/v1/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {_API_KEY}",
},
json={
"model": "MiniMax-M2.7",
"messages": [{"role": "user", "content": 'Say "test passed"'}],
"max_tokens": 20,
"temperature": 1.0,
},
timeout=30.0,
)
assert response.status_code == 200, f"API returned {response.status_code}: {response.text}"
data = response.json()
assert data["choices"][0]["message"]["content"], "Response content must not be empty"


@pytest.mark.timeout(30)
def test_minimax_streaming():
"""Verify streaming works via the MiniMax OpenAI-compatible API."""
import httpx

with httpx.stream(
"POST",
"https://api.minimax.io/v1/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {_API_KEY}",
},
json={
"model": "MiniMax-M2.7",
"messages": [{"role": "user", "content": "Say hello"}],
"max_tokens": 20,
"temperature": 1.0,
"stream": True,
},
timeout=30.0,
) as response:
assert response.status_code == 200
chunks = list(response.iter_lines())
assert len(chunks) > 0, "Must receive at least one streaming chunk"
127 changes: 127 additions & 0 deletions tests/test_minimax_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""Unit tests for MiniMax provider integration."""

from __future__ import annotations

import os
import importlib
from unittest import mock

import pytest


# ---------------------------------------------------------------------------
# onboard.py – provider registration
# ---------------------------------------------------------------------------

class TestOnboardProviderRegistration:
"""Verify MiniMax appears in the PROVIDERS list with correct metadata."""

def _load_providers(self):
import onboard
importlib.reload(onboard)
return onboard.PROVIDERS

def test_minimax_provider_present(self):
providers = self._load_providers()
names = [p["name"] for p in providers]
assert "MiniMax" in names, "MiniMax must be listed in PROVIDERS"

def test_minimax_env_key(self):
providers = self._load_providers()
mm = next(p for p in providers if p["name"] == "MiniMax")
assert mm["env_key"] == "MINIMAX_API_KEY"

def test_minimax_default_model(self):
providers = self._load_providers()
mm = next(p for p in providers if p["name"] == "MiniMax")
assert mm["default_model"] == "minimax/MiniMax-M2.7"

def test_minimax_url(self):
providers = self._load_providers()
mm = next(p for p in providers if p["name"] == "MiniMax")
assert mm["url"], "URL must be set"
assert "minimax" in mm["url"].lower() or "minimax" in mm["url"].lower()


# ---------------------------------------------------------------------------
# config.py – model resolution
# ---------------------------------------------------------------------------

class TestConfigModelResolution:
"""Verify config.py resolves MiniMax model strings correctly."""

def test_minimax_model_is_not_openai(self):
"""minimax/MiniMax-M2.7 contains '/' so is_openai_provider() should return False."""
with mock.patch.dict(os.environ, {"DEFAULT_MODEL": "minimax/MiniMax-M2.7"}):
import config
importlib.reload(config)
assert config.is_openai_provider() is False

def test_minimax_model_resolved_through_litellm(self):
"""_resolve should wrap minimax/MiniMax-M2.7 via LitellmModel."""
import config
importlib.reload(config)
result = config._resolve("minimax/MiniMax-M2.7")
# If agency_swarm is installed, result is a LitellmModel; otherwise
# the bare string is returned as fallback.
try:
from agency_swarm import LitellmModel
assert isinstance(result, LitellmModel)
except ImportError:
assert result == "minimax/MiniMax-M2.7"

def test_minimax_highspeed_model_resolved(self):
"""Highspeed variant should also resolve correctly."""
import config
importlib.reload(config)
result = config._resolve("minimax/MiniMax-M2.7-highspeed")
try:
from agency_swarm import LitellmModel
assert isinstance(result, LitellmModel)
except ImportError:
assert result == "minimax/MiniMax-M2.7-highspeed"

def test_get_default_model_with_minimax(self):
"""get_default_model() should return resolved MiniMax model when DEFAULT_MODEL is set."""
with mock.patch.dict(os.environ, {"DEFAULT_MODEL": "minimax/MiniMax-M2.7"}):
import config
importlib.reload(config)
result = config.get_default_model()
# Should not be the raw string (it should be resolved)
try:
from agency_swarm import LitellmModel
assert isinstance(result, LitellmModel)
except ImportError:
assert result == "minimax/MiniMax-M2.7"

def test_litellm_prefix_stripped_correctly(self):
"""'litellm/minimax/MiniMax-M2.7' should strip to 'minimax/MiniMax-M2.7' for LitellmModel."""
import config
importlib.reload(config)
result = config._resolve("litellm/minimax/MiniMax-M2.7")
try:
from agency_swarm import LitellmModel
assert isinstance(result, LitellmModel)
except ImportError:
# When agency_swarm is not installed, _resolve returns the original string
assert result == "litellm/minimax/MiniMax-M2.7"


# ---------------------------------------------------------------------------
# .env.example – documentation
# ---------------------------------------------------------------------------

class TestEnvExample:
"""Verify .env.example documents MINIMAX_API_KEY."""

def test_minimax_api_key_documented(self):
from pathlib import Path
env_example = Path(__file__).resolve().parent.parent / ".env.example"
content = env_example.read_text(encoding="utf-8")
assert "MINIMAX_API_KEY" in content, "MINIMAX_API_KEY must be documented in .env.example"

def test_minimax_default_model_example(self):
from pathlib import Path
env_example = Path(__file__).resolve().parent.parent / ".env.example"
content = env_example.read_text(encoding="utf-8")
assert "minimax/MiniMax-M2.7" in content, "DEFAULT_MODEL example for MiniMax must be in .env.example"