From b1aefb364a4e06361d8abbf7c21af5e903745a9c Mon Sep 17 00:00:00 2001 From: calesthio Date: Thu, 13 Aug 2026 09:02:22 -0700 Subject: [PATCH] fix: complete MiniMax image provider contracts --- .env.example | 7 ++++ docs/PROVIDERS.md | 35 ++++++++++++++++ tests/tools/test_minimax_image.py | 69 +++++++++++++++++++++++++++++++ tools/graphics/minimax_image.py | 10 ++++- 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 51d973e4..f4788da5 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,13 @@ FAL_KEY= # Alias for FAL_KEY (some SDKs/docs use this name); either one is read. FAL_AI_API_KEY= +# --- MiniMax official direct API --- +# First-party image generation (image-01 / image-01-live). +# Get one at https://platform.minimax.io/user-center/basic-information/interface-key +MINIMAX_API_KEY= +# Optional: global (default) or cn. +MINIMAX_REGION=global + # --- Replicate --- # Replicate-hosted video gen (seedance_replicate). Needed to make the # Replicate-backed Seedance path selectable alongside the fal.ai one. diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 7ad51bad..0574a12a 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -50,6 +50,7 @@ AZURE_SPEECH_REGION= # Speech resource region, e.g. eastus # MULTI-MODEL GATEWAY (one key, 6+ tools) FAL_KEY= # FLUX, Recraft, Kling, Veo, MiniMax video +MINIMAX_API_KEY= # MiniMax first-party image generation # KLING OFFICIAL DIRECT API KLING_API_KEY= # Official Kling video, image, TTS, avatar, lip sync @@ -227,6 +228,40 @@ No subscription — pure pay-as-you-go, no minimum spend. --- +### MiniMax — Official Direct Image API + +> **Low-cost first-party image generation.** The direct MiniMax API supports +> seeded text-to-image, character subject references, custom dimensions, and +> global or mainland-China routing without a gateway. + +**Tool unlocked:** `minimax_image` + +**Env var:** `MINIMAX_API_KEY` + +**Optional region:** `MINIMAX_REGION=global` (default) or `cn` + +#### Setup + +1. Create a MiniMax Open Platform account. +2. Generate an API key in the account's API-key page. +3. Add `MINIMAX_API_KEY=...` to `.env`. +4. For a mainland-China account, also set `MINIMAX_REGION=cn`. + +#### Pricing + +| Models | Global pay-as-you-go price | +|--------|----------------------------| +| `image-01`, `image-01-live` | $0.0035 per generated image | + +MiniMax also offers subscription token plans with included daily image quota. +OpenMontage conservatively reports the standard pay-as-you-go amount in cost +estimates and generation results. + +The tool is automatically discoverable through `image_selector`; choose it +with `preferred_provider: "minimax"`. + +--- + ### Kling Official — Direct API > **Official Kling path.** This is separate from `kling_video` via fal.ai: it uses Kling's official `Authorization: Bearer ` API, provider name `kling_official`, and direct Classic/Turbo/Omni task protocols. diff --git a/tests/tools/test_minimax_image.py b/tests/tools/test_minimax_image.py index ed49af19..7fcf4777 100644 --- a/tests/tools/test_minimax_image.py +++ b/tests/tools/test_minimax_image.py @@ -54,6 +54,75 @@ def test_status_requires_api_key(monkeypatch: pytest.MonkeyPatch) -> None: assert tool.get_status() == ToolStatus.AVAILABLE +def test_cost_estimate_and_result_report_paid_images( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + monkeypatch.setenv("MINIMAX_API_KEY", "test-key") + monkeypatch.setattr( + requests, + "post", + lambda *args, **kwargs: FakeResponse( + json_data={ + "data": { + "image_base64": [ + base64.b64encode(b"one").decode("ascii"), + base64.b64encode(b"two").decode("ascii"), + ] + }, + "base_resp": {"status_code": 0}, + } + ), + ) + + tool = MiniMaxImage() + inputs = { + "prompt": "A lighthouse at dusk", + "response_format": "base64", + "n": 2, + "output_path": str(tmp_path / "image.png"), + } + assert tool.estimate_cost(inputs) == pytest.approx(0.007) + result = tool.execute(inputs) + assert result.success, result.error + assert result.cost_usd == pytest.approx(0.007) + + +def test_image_selector_can_route_to_minimax( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + from tools.graphics.image_selector import ImageSelector + + monkeypatch.setenv("MINIMAX_API_KEY", "test-key") + monkeypatch.setattr( + requests, + "post", + lambda *args, **kwargs: FakeResponse( + json_data={ + "data": { + "image_base64": [base64.b64encode(b"image").decode("ascii")] + }, + "base_resp": {"status_code": 0}, + } + ), + ) + tool = MiniMaxImage() + selector = ImageSelector() + monkeypatch.setattr(selector, "_providers", lambda: [tool]) + + result = selector.execute( + { + "prompt": "A lighthouse at dusk", + "preferred_provider": "minimax", + "response_format": "base64", + "output_path": str(tmp_path / "selected.png"), + } + ) + + assert result.success, result.error + assert result.data["selected_provider"] == "minimax" + assert result.data["selected_tool"] == "minimax_image" + + @pytest.mark.parametrize( ("region", "expected_base_url"), [ diff --git a/tools/graphics/minimax_image.py b/tools/graphics/minimax_image.py index 47bac617..2c879223 100644 --- a/tools/graphics/minimax_image.py +++ b/tools/graphics/minimax_image.py @@ -24,6 +24,8 @@ from tools.base_tool import ( MODELS = ["image-01", "image-01-live"] DEFAULT_MODEL = "image-01" DEFAULT_REGION = "global" +# Official global pay-as-you-go rate for image-01/image-01-live. +PRICE_PER_IMAGE_USD = 0.0035 REGION_BASE_URLS = { "global": "https://api.minimax.io", "global_en": "https://api.minimax.io", @@ -48,7 +50,9 @@ class MiniMaxImage(BaseTool): "Set MINIMAX_API_KEY to your MiniMax API key. " "Optionally set MINIMAX_REGION to global or cn." ) - agent_skills = ["flux-best-practices"] + # MiniMax is not a FLUX model. Use the provider-neutral visual direction + # skill until a dedicated MiniMax prompting skill is available. + agent_skills = ["visual-style"] capabilities = ["generate_image", "text_to_image"] supports = { @@ -209,6 +213,9 @@ class MiniMaxImage(BaseTool): def _safe_error(exc: Exception, api_key: str) -> str: return str(exc).replace(api_key, "[redacted]") if api_key else str(exc) + def estimate_cost(self, inputs: dict[str, Any]) -> float: + return PRICE_PER_IMAGE_USD * int(inputs.get("n", 1)) + def execute(self, inputs: dict[str, Any]) -> ToolResult: api_key = os.environ.get("MINIMAX_API_KEY", "") if not api_key: @@ -285,6 +292,7 @@ class MiniMaxImage(BaseTool): "request_id": data.get("id"), }, artifacts=outputs, + cost_usd=self.estimate_cost(inputs), duration_seconds=round(time.time() - start, 2), model=payload["model"], )