From 9f9b87b0f1472ba3abb0cfa7706f279b560d50d6 Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:49:24 +0800 Subject: [PATCH 1/2] feat: add direct MiniMax provider models --- .env.example | 23 +++++ Dockerfile | 5 +- README.md | 18 ++++ agentfield-package.yaml | 14 ++- docs/deployment.md | 12 +++ go/Dockerfile | 5 +- opencode.json | 120 ++++++++++++++++++++++++++ tests/test_minimax_provider_config.py | 97 +++++++++++++++++++++ 8 files changed, 287 insertions(+), 7 deletions(-) create mode 100644 opencode.json create mode 100644 tests/test_minimax_provider_config.py diff --git a/.env.example b/.env.example index 1abc19cd..69120470 100644 --- a/.env.example +++ b/.env.example @@ -35,6 +35,21 @@ ANTHROPIC_API_KEY=sk-ant-api03-... # Get your key at https://z.ai/manage-apikey/apikey-list # ZHIPU_API_KEY=... +# MiniMax direct access with the open runtime. The image registers global and +# China OpenAI-compatible providers for MiniMax-M3 and MiniMax-M2.7. +# MINIMAX_API_KEY=... +# SWE_DEFAULT_RUNTIME=open_code +# SWE_DEFAULT_MODEL=minimax-global-openai/MiniMax-M3 +# China endpoint: SWE_DEFAULT_MODEL=minimax-cn-openai/MiniMax-M3 + +# MiniMax direct access with the Claude runtime. Claude Code appends +# /v1/messages to this Anthropic-compatible base URL. +# ANTHROPIC_AUTH_TOKEN=... +# ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic +# China endpoint: ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic +# SWE_DEFAULT_RUNTIME=claude_code +# SWE_DEFAULT_MODEL=MiniMax-M3 + # Note: Can reuse ANTHROPIC_API_KEY from above for Claude models via OpenCode # --- Optional: Go node direct-LLM path (QA synthesizer) --- @@ -153,6 +168,14 @@ GH_TOKEN=ghp_... # Available open runtime model IDs (format: provider/model-name): # deepseek/deepseek-chat # DeepSeek via OpenRouter # minimax/minimax-m2.5 # MiniMax M2.5 via OpenRouter +# minimax-global-openai/MiniMax-M3 # MiniMax global OpenAI-compatible API +# minimax-global-openai/MiniMax-M2.7 # MiniMax global OpenAI-compatible API +# minimax-cn-openai/MiniMax-M3 # MiniMax China OpenAI-compatible API +# minimax-cn-openai/MiniMax-M2.7 # MiniMax China OpenAI-compatible API +# minimax/MiniMax-M3 # MiniMax global Anthropic-compatible API +# minimax/MiniMax-M2.7 # MiniMax global Anthropic-compatible API +# minimax-cn/MiniMax-M3 # MiniMax China Anthropic-compatible API +# minimax-cn/MiniMax-M2.7 # MiniMax China Anthropic-compatible API # qwen/qwen-2.5-72b-instruct # Qwen via OpenRouter # openai/gpt-4o # GPT-4 via OpenAI # anthropic/claude-sonnet-4 # Claude via Anthropic diff --git a/Dockerfile b/Dockerfile index 9687f531..639efcae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,9 +65,8 @@ ENV PATH="/root/.opencode/bin:${PATH}" # env override has *some* value to interpolate. Railway / docker-compose # overrides win because their env injects after the image's ENV. ENV HARNESS_MODEL=openrouter/moonshotai/kimi-k2.6 -RUN mkdir -p /root/.config/opencode && \ - echo '{"$schema":"https://opencode.ai/config.json","model":"{env:HARNESS_MODEL}","small_model":"{env:HARNESS_MODEL}","provider":{"openrouter":{"options":{"apiKey":"{env:OPENROUTER_API_KEY}"}}}}' \ - > /root/.config/opencode/opencode.json +RUN mkdir -p /root/.config/opencode +COPY opencode.json /root/.config/opencode/opencode.json # Git identity — env vars take highest precedence and are inherited by all # subprocesses including Claude Code agent instances spawned by the SDK diff --git a/README.md b/README.md index 39e8bee5..528ee4fa 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,24 @@ JSON For OpenRouter with `open_code`, use model IDs in `openrouter//` format (for example `openrouter/minimax/minimax-m2.5`). +### MiniMax direct providers + +The Docker images include direct MiniMax provider entries for both supported regions and API compatibility modes. `MiniMax-M3` and `MiniMax-M2.7` are available in every entry. + +| Region | OpenAI-compatible `open_code` model | Anthropic-compatible `open_code` model | Anthropic base URL for `claude_code` | +|---|---|---|---| +| Global | `minimax-global-openai/MiniMax-M3` | `minimax/MiniMax-M3` | `https://api.minimax.io/anthropic` | +| China | `minimax-cn-openai/MiniMax-M3` | `minimax-cn/MiniMax-M3` | `https://api.minimaxi.com/anthropic` | + +| Model | Context window | Input modalities | Thinking | Standard price per million tokens | +|---|---:|---|---|---| +| `MiniMax-M3` | 1,000,000 | text, image, video | adaptive or disabled | $0.30 input / $1.20 output / $0.06 cache read | +| `MiniMax-M2.7` | 204,800 | text | always on | $0.30 input / $1.20 output / $0.06 cache read / $0.375 cache write | + +For the direct OpenAI-compatible path, set `MINIMAX_API_KEY`, use `runtime: "open_code"`, and select one of the model IDs above. The configured OpenAI-compatible base URLs are `https://api.minimax.io/v1` and `https://api.minimaxi.com/v1`. + +For the direct Anthropic-compatible Claude path, set `ANTHROPIC_AUTH_TOKEN`, set `ANTHROPIC_BASE_URL` to the regional `/anthropic` URL shown above, use `runtime: "claude_code"`, and select `MiniMax-M3` or `MiniMax-M2.7`. Do not append `/v1`; Claude Code adds `/v1/messages` to the configured base URL. + For Codex with ChatGPT subscription auth, install the Codex CLI on the host, run `codex login`, leave `OPENAI_API_KEY` unset for this process, and set `SWE_CODEX_AUTH_MODE=chatgpt` or `auto`. For OpenAI API-platform billing, set `SWE_CODEX_AUTH_MODE=api_key` and `OPENAI_API_KEY`. > **Codex deployments using the Docker image must set `SWE_DEFAULT_MODEL=gpt-5.3-codex` on the environment** (or pass `models: {"default": "gpt-5.3-codex"}` in every build's `config`). The image bakes `HARNESS_MODEL=openrouter/moonshotai/kimi-k2.6` as an OpenCode fallback, and SWE-AF's model-resolution env cascade reads `HARNESS_MODEL` — so without `SWE_DEFAULT_MODEL` set, the Codex CLI receives an OpenRouter model id it can't handle and the Product Manager reasoner fails in ~13s. Setting `SWE_DEFAULT_MODEL` makes the cascade pin every role to the Codex model. diff --git a/agentfield-package.yaml b/agentfield-package.yaml index 1a393a2f..7d1b4954 100644 --- a/agentfield-package.yaml +++ b/agentfield-package.yaml @@ -14,7 +14,7 @@ agent_node: user_environment: require_one_of: - # SWE-AF runs on either Claude (Anthropic) or open models via OpenRouter. + # SWE-AF runs on Claude-compatible APIs or open models via OpenCode. # Provide one. With only an OpenRouter key it auto-selects the open_code # runtime and defaults to openrouter/deepseek/deepseek-v4-flash. - id: llm_provider @@ -28,6 +28,14 @@ user_environment: description: OpenRouter API key (DeepSeek/Qwen/Llama/… — 200+ models) type: secret scope: global + - name: MINIMAX_API_KEY + description: MiniMax API key for direct OpenCode providers + type: secret + scope: global + - name: ANTHROPIC_AUTH_TOKEN + description: Anthropic-compatible auth token for the Claude runtime + type: secret + scope: global required: # SWE-AF's core loop clones a repo, pushes a branch, and opens a pull # request — all of which need GitHub write access. `gh`/`git` read this @@ -38,8 +46,12 @@ user_environment: type: secret scope: global optional: + - name: SWE_DEFAULT_RUNTIME + description: Default runtime (claude_code, open_code, or codex) - name: SWE_DEFAULT_MODEL description: Override the model id for every role (e.g. openrouter/deepseek/deepseek-v4-flash) + - name: ANTHROPIC_BASE_URL + description: Anthropic-compatible base URL for the Claude runtime - name: AGENTFIELD_SERVER description: Control-plane URL default: http://localhost:8080 diff --git a/docs/deployment.md b/docs/deployment.md index c8e97049..833940f5 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -34,6 +34,14 @@ cp .env.example .env | `OPENROUTER_API_KEY` | OpenRouter API key (200+ models) | | `OPENAI_API_KEY` | OpenAI API key | | `GOOGLE_API_KEY` | Google Gemini API key | +| `MINIMAX_API_KEY` | MiniMax API key for direct OpenAI- or Anthropic-compatible `open_code` providers | + +**For direct MiniMax with the Claude runtime:** + +| Variable | Purpose | +|---|---| +| `ANTHROPIC_AUTH_TOKEN` | MiniMax API key used by Claude Code | +| `ANTHROPIC_BASE_URL` | `https://api.minimax.io/anthropic` (global) or `https://api.minimaxi.com/anthropic` (China) | **For Codex CLI runtime:** @@ -50,6 +58,10 @@ cp .env.example .env | `AGENTFIELD_SERVER` | Control plane URL | `http://control-plane:8080` (Docker) | | `NODE_ID` | Agent node identifier | `swe-planner` | | `PORT` | Agent listen port | `8003` | +| `SWE_DEFAULT_RUNTIME` | Default runtime: `claude_code`, `open_code`, or `codex` | `claude_code` | +| `SWE_DEFAULT_MODEL` | Default model ID for every role | runtime-specific | + +The image registers `MiniMax-M3` and `MiniMax-M2.7` under the direct OpenAI-compatible providers `minimax-global-openai` and `minimax-cn-openai`. OpenCode's Anthropic-compatible entries are `minimax` and `minimax-cn`. Use the regional OpenAI base URLs `https://api.minimax.io/v1` and `https://api.minimaxi.com/v1`; use the Anthropic base URLs exactly as shown above without appending `/v1`. ### Package Versions diff --git a/go/Dockerfile b/go/Dockerfile index 3f3ae19d..7933f0a3 100644 --- a/go/Dockerfile +++ b/go/Dockerfile @@ -125,9 +125,8 @@ ENV PATH="/root/.opencode/bin:${PATH}" # value inside the image so a fresh container has *some* value to interpolate; # Railway / docker-compose overrides win because their env injects afterward. ENV HARNESS_MODEL=openrouter/moonshotai/kimi-k2.6 -RUN mkdir -p /root/.config/opencode && \ - echo '{"$schema":"https://opencode.ai/config.json","model":"{env:HARNESS_MODEL}","small_model":"{env:HARNESS_MODEL}","provider":{"openrouter":{"options":{"apiKey":"{env:OPENROUTER_API_KEY}"}}}}' \ - > /root/.config/opencode/opencode.json +RUN mkdir -p /root/.config/opencode +COPY opencode.json /root/.config/opencode/opencode.json # Git identity — env vars take highest precedence and are inherited by all # subprocesses (including the CLIs the harness spawns). diff --git a/opencode.json b/opencode.json new file mode 100644 index 00000000..f5422f07 --- /dev/null +++ b/opencode.json @@ -0,0 +1,120 @@ +{ + "$schema": "https://opencode.ai/config.json", + "model": "{env:HARNESS_MODEL}", + "small_model": "{env:HARNESS_MODEL}", + "provider": { + "openrouter": { + "options": { + "apiKey": "{env:OPENROUTER_API_KEY}" + } + }, + "minimax-global-openai": { + "name": "MiniMax Global (OpenAI-compatible)", + "env": [ + "MINIMAX_API_KEY" + ], + "npm": "@ai-sdk/openai-compatible", + "options": { + "apiKey": "{env:MINIMAX_API_KEY}", + "baseURL": "https://api.minimax.io/v1" + }, + "models": { + "MiniMax-M3": { + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "attachment": true, + "reasoning": true, + "cost": { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06 + }, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + } + }, + "MiniMax-M2.7": { + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "attachment": false, + "reasoning": true, + "cost": { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06, + "cache_write": 0.375 + }, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + } + } + } + }, + "minimax-cn-openai": { + "name": "MiniMax China (OpenAI-compatible)", + "env": [ + "MINIMAX_API_KEY" + ], + "npm": "@ai-sdk/openai-compatible", + "options": { + "apiKey": "{env:MINIMAX_API_KEY}", + "baseURL": "https://api.minimaxi.com/v1" + }, + "models": { + "MiniMax-M3": { + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "attachment": true, + "reasoning": true, + "cost": { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06 + }, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + } + }, + "MiniMax-M2.7": { + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "attachment": false, + "reasoning": true, + "cost": { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06, + "cache_write": 0.375 + }, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + } + } + } + } + } +} diff --git a/tests/test_minimax_provider_config.py b/tests/test_minimax_provider_config.py new file mode 100644 index 00000000..20cb7af0 --- /dev/null +++ b/tests/test_minimax_provider_config.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parent.parent +OPENCODE_CONFIG = ROOT / "opencode.json" +DOCKERFILES = (ROOT / "Dockerfile", ROOT / "go" / "Dockerfile") + +MODEL_IDS = {"MiniMax-M3", "MiniMax-M2.7"} +OPENAI_PROVIDERS = { + "minimax-global-openai": "https://api.minimax.io/v1", + "minimax-cn-openai": "https://api.minimaxi.com/v1", +} + + +@pytest.fixture(scope="module") +def opencode_config() -> dict[str, object]: + return json.loads(OPENCODE_CONFIG.read_text()) + + +def test_both_images_install_the_shared_opencode_config() -> None: + expected = "COPY opencode.json /root/.config/opencode/opencode.json" + for dockerfile in DOCKERFILES: + assert expected in dockerfile.read_text() + + +@pytest.mark.parametrize(("provider_id", "base_url"), OPENAI_PROVIDERS.items()) +def test_minimax_openai_provider_registry( + opencode_config: dict[str, object], provider_id: str, base_url: str +) -> None: + providers = opencode_config["provider"] + assert isinstance(providers, dict) + provider = providers[provider_id] + + assert provider["npm"] == "@ai-sdk/openai-compatible" + assert provider["env"] == ["MINIMAX_API_KEY"] + assert provider["options"] == { + "apiKey": "{env:MINIMAX_API_KEY}", + "baseURL": base_url, + } + assert set(provider["models"]) == MODEL_IDS + + +def test_minimax_model_metadata_matches_target_config( + opencode_config: dict[str, object], +) -> None: + providers = opencode_config["provider"] + assert isinstance(providers, dict) + + for provider_id in OPENAI_PROVIDERS: + models = providers[provider_id]["models"] + assert models["MiniMax-M3"]["cost"] == { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06, + } + assert models["MiniMax-M3"]["modalities"]["input"] == [ + "text", + "image", + "video", + ] + assert models["MiniMax-M3"]["reasoning"] is True + + assert models["MiniMax-M2.7"]["cost"] == { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06, + "cache_write": 0.375, + } + assert models["MiniMax-M2.7"]["modalities"]["input"] == ["text"] + assert models["MiniMax-M2.7"]["reasoning"] is True + + +def test_minimax_anthropic_endpoints_and_model_ids_are_documented() -> None: + docs = (ROOT / "README.md").read_text() + (ROOT / ".env.example").read_text() + + for value in ( + "https://api.minimax.io/anthropic", + "https://api.minimaxi.com/anthropic", + "minimax/MiniMax-M3", + "minimax/MiniMax-M2.7", + "minimax-cn/MiniMax-M3", + "minimax-cn/MiniMax-M2.7", + "1,000,000", + "204,800", + "adaptive or disabled", + "always on", + "$0.30 input / $1.20 output / $0.06 cache read", + "$0.30 input / $1.20 output / $0.06 cache read / $0.375 cache write", + ): + assert value in docs + + assert "https://api.minimax.io/anthropic/v1" not in docs + assert "https://api.minimaxi.com/anthropic/v1" not in docs From 30ee66236aafc3d0eddb25ad8abe31da542dd44f Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:23:11 +0800 Subject: [PATCH 2/2] fix: complete MiniMax provider coverage --- .env.example | 12 +++-- README.md | 23 ++++---- agentfield-package.yaml | 2 +- docs/deployment.md | 4 +- opencode.json | 78 +++++++++++++++++++++++++++ tests/test_minimax_provider_config.py | 44 ++++++++++++--- 6 files changed, 139 insertions(+), 24 deletions(-) diff --git a/.env.example b/.env.example index 69120470..7436fa72 100644 --- a/.env.example +++ b/.env.example @@ -36,11 +36,15 @@ ANTHROPIC_API_KEY=sk-ant-api03-... # ZHIPU_API_KEY=... # MiniMax direct access with the open runtime. The image registers global and -# China OpenAI-compatible providers for MiniMax-M3 and MiniMax-M2.7. +# China OpenAI-compatible providers plus an Anthropic-compatible provider for +# MiniMax-M3 and MiniMax-M2.7. # MINIMAX_API_KEY=... # SWE_DEFAULT_RUNTIME=open_code # SWE_DEFAULT_MODEL=minimax-global-openai/MiniMax-M3 # China endpoint: SWE_DEFAULT_MODEL=minimax-cn-openai/MiniMax-M3 +# Anthropic-compatible endpoint: +# ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic +# SWE_DEFAULT_MODEL=minimax-anthropic/MiniMax-M3 # MiniMax direct access with the Claude runtime. Claude Code appends # /v1/messages to this Anthropic-compatible base URL. @@ -172,10 +176,8 @@ GH_TOKEN=ghp_... # minimax-global-openai/MiniMax-M2.7 # MiniMax global OpenAI-compatible API # minimax-cn-openai/MiniMax-M3 # MiniMax China OpenAI-compatible API # minimax-cn-openai/MiniMax-M2.7 # MiniMax China OpenAI-compatible API -# minimax/MiniMax-M3 # MiniMax global Anthropic-compatible API -# minimax/MiniMax-M2.7 # MiniMax global Anthropic-compatible API -# minimax-cn/MiniMax-M3 # MiniMax China Anthropic-compatible API -# minimax-cn/MiniMax-M2.7 # MiniMax China Anthropic-compatible API +# minimax-anthropic/MiniMax-M3 # MiniMax Anthropic-compatible API +# minimax-anthropic/MiniMax-M2.7 # MiniMax Anthropic-compatible API # qwen/qwen-2.5-72b-instruct # Qwen via OpenRouter # openai/gpt-4o # GPT-4 via OpenAI # anthropic/claude-sonnet-4 # Claude via Anthropic diff --git a/README.md b/README.md index 528ee4fa..ef22e85d 100644 --- a/README.md +++ b/README.md @@ -377,19 +377,24 @@ For OpenRouter with `open_code`, use model IDs in `openrouter// The Docker images include direct MiniMax provider entries for both supported regions and API compatibility modes. `MiniMax-M3` and `MiniMax-M2.7` are available in every entry. -| Region | OpenAI-compatible `open_code` model | Anthropic-compatible `open_code` model | Anthropic base URL for `claude_code` | +| Region | OpenAI-compatible `open_code` model IDs | Anthropic-compatible `open_code` model IDs | Anthropic base URL | |---|---|---|---| -| Global | `minimax-global-openai/MiniMax-M3` | `minimax/MiniMax-M3` | `https://api.minimax.io/anthropic` | -| China | `minimax-cn-openai/MiniMax-M3` | `minimax-cn/MiniMax-M3` | `https://api.minimaxi.com/anthropic` | +| Global | `minimax-global-openai/MiniMax-M3`, `minimax-global-openai/MiniMax-M2.7` | `minimax-anthropic/MiniMax-M3`, `minimax-anthropic/MiniMax-M2.7` | `https://api.minimax.io/anthropic` | +| China | `minimax-cn-openai/MiniMax-M3`, `minimax-cn-openai/MiniMax-M2.7` | `minimax-anthropic/MiniMax-M3`, `minimax-anthropic/MiniMax-M2.7` | `https://api.minimaxi.com/anthropic` | -| Model | Context window | Input modalities | Thinking | Standard price per million tokens | -|---|---:|---|---|---| -| `MiniMax-M3` | 1,000,000 | text, image, video | adaptive or disabled | $0.30 input / $1.20 output / $0.06 cache read | -| `MiniMax-M2.7` | 204,800 | text | always on | $0.30 input / $1.20 output / $0.06 cache read / $0.375 cache write | +| Model | Context window | Input modalities | Thinking | Service tier | Input range | Input / output / cache read / cache write per million tokens | +|---|---:|---|---|---|---|---| +| `MiniMax-M3` | 1,000,000 | text, image, video | adaptive or disabled | standard | up to 512,000 | $0.30 / $1.20 / $0.06 / not charged | +| `MiniMax-M3` | 1,000,000 | text, image, video | adaptive or disabled | standard | above 512,000 | $0.60 / $2.40 / $0.12 / not charged | +| `MiniMax-M3` | 1,000,000 | text, image, video | adaptive or disabled | priority | up to 512,000 | $0.45 / $1.80 / $0.09 / not charged | +| `MiniMax-M3` | 1,000,000 | text, image, video | adaptive or disabled | priority | above 512,000 | $0.90 / $3.60 / $0.18 / not charged | +| `MiniMax-M2.7` | 204,800 | text | always on | standard | all inputs | $0.30 / $1.20 / $0.06 / $0.375 | -For the direct OpenAI-compatible path, set `MINIMAX_API_KEY`, use `runtime: "open_code"`, and select one of the model IDs above. The configured OpenAI-compatible base URLs are `https://api.minimax.io/v1` and `https://api.minimaxi.com/v1`. +For the direct OpenAI-compatible path, set `MINIMAX_API_KEY`, use `runtime: "open_code"`, and select one of the `minimax-global-openai/*` or `minimax-cn-openai/*` model IDs above. The configured OpenAI-compatible base URLs are `https://api.minimax.io/v1` and `https://api.minimaxi.com/v1`. -For the direct Anthropic-compatible Claude path, set `ANTHROPIC_AUTH_TOKEN`, set `ANTHROPIC_BASE_URL` to the regional `/anthropic` URL shown above, use `runtime: "claude_code"`, and select `MiniMax-M3` or `MiniMax-M2.7`. Do not append `/v1`; Claude Code adds `/v1/messages` to the configured base URL. +For the Anthropic-compatible OpenCode path, set `MINIMAX_API_KEY`, set `ANTHROPIC_BASE_URL` to either regional `/anthropic` URL shown above, use `runtime: "open_code"`, and select `minimax-anthropic/MiniMax-M3` or `minimax-anthropic/MiniMax-M2.7`. The OpenCode adapter derives `/v1` internally; keep the configured base URL at `/anthropic`. + +For the Anthropic-compatible Claude path, set `ANTHROPIC_AUTH_TOKEN`, set `ANTHROPIC_BASE_URL` to the regional `/anthropic` URL shown above, use `runtime: "claude_code"`, and select `MiniMax-M3` or `MiniMax-M2.7`. Do not append `/v1`; Claude Code adds `/v1/messages` to the configured base URL. For Codex with ChatGPT subscription auth, install the Codex CLI on the host, run `codex login`, leave `OPENAI_API_KEY` unset for this process, and set `SWE_CODEX_AUTH_MODE=chatgpt` or `auto`. For OpenAI API-platform billing, set `SWE_CODEX_AUTH_MODE=api_key` and `OPENAI_API_KEY`. diff --git a/agentfield-package.yaml b/agentfield-package.yaml index 7d1b4954..6ee49824 100644 --- a/agentfield-package.yaml +++ b/agentfield-package.yaml @@ -51,7 +51,7 @@ user_environment: - name: SWE_DEFAULT_MODEL description: Override the model id for every role (e.g. openrouter/deepseek/deepseek-v4-flash) - name: ANTHROPIC_BASE_URL - description: Anthropic-compatible base URL for the Claude runtime + description: Anthropic-compatible base URL for OpenCode or Claude Code - name: AGENTFIELD_SERVER description: Control-plane URL default: http://localhost:8080 diff --git a/docs/deployment.md b/docs/deployment.md index 833940f5..89353339 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -34,7 +34,7 @@ cp .env.example .env | `OPENROUTER_API_KEY` | OpenRouter API key (200+ models) | | `OPENAI_API_KEY` | OpenAI API key | | `GOOGLE_API_KEY` | Google Gemini API key | -| `MINIMAX_API_KEY` | MiniMax API key for direct OpenAI- or Anthropic-compatible `open_code` providers | +| `MINIMAX_API_KEY` | MiniMax API key for direct OpenAI- and Anthropic-compatible `open_code` providers | **For direct MiniMax with the Claude runtime:** @@ -61,7 +61,7 @@ cp .env.example .env | `SWE_DEFAULT_RUNTIME` | Default runtime: `claude_code`, `open_code`, or `codex` | `claude_code` | | `SWE_DEFAULT_MODEL` | Default model ID for every role | runtime-specific | -The image registers `MiniMax-M3` and `MiniMax-M2.7` under the direct OpenAI-compatible providers `minimax-global-openai` and `minimax-cn-openai`. OpenCode's Anthropic-compatible entries are `minimax` and `minimax-cn`. Use the regional OpenAI base URLs `https://api.minimax.io/v1` and `https://api.minimaxi.com/v1`; use the Anthropic base URLs exactly as shown above without appending `/v1`. +The image registers `MiniMax-M3` and `MiniMax-M2.7` under the direct OpenAI-compatible providers `minimax-global-openai` and `minimax-cn-openai`, and under the Anthropic-compatible OpenCode provider `minimax-anthropic`. Set `ANTHROPIC_BASE_URL` to either regional `/anthropic` URL shown above; the OpenCode adapter derives `/v1` internally. Use the regional OpenAI base URLs `https://api.minimax.io/v1` and `https://api.minimaxi.com/v1`; keep the external Anthropic base URL exactly as shown without appending `/v1`. ### Package Versions diff --git a/opencode.json b/opencode.json index f5422f07..1feac05a 100644 --- a/opencode.json +++ b/opencode.json @@ -24,6 +24,10 @@ "name": "MiniMax-M3", "attachment": true, "reasoning": true, + "limit": { + "context": 1000000, + "output": 524288 + }, "cost": { "input": 0.3, "output": 1.2, @@ -45,6 +49,10 @@ "name": "MiniMax-M2.7", "attachment": false, "reasoning": true, + "limit": { + "context": 204800, + "output": 204800 + }, "cost": { "input": 0.3, "output": 1.2, @@ -78,6 +86,72 @@ "name": "MiniMax-M3", "attachment": true, "reasoning": true, + "limit": { + "context": 1000000, + "output": 524288 + }, + "cost": { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06 + }, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + } + }, + "MiniMax-M2.7": { + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "attachment": false, + "reasoning": true, + "limit": { + "context": 204800, + "output": 204800 + }, + "cost": { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06, + "cache_write": 0.375 + }, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + } + } + } + }, + "minimax-anthropic": { + "name": "MiniMax (Anthropic-compatible)", + "env": [ + "MINIMAX_API_KEY" + ], + "npm": "@ai-sdk/anthropic", + "options": { + "apiKey": "{env:MINIMAX_API_KEY}", + "baseURL": "{env:ANTHROPIC_BASE_URL}/v1" + }, + "models": { + "MiniMax-M3": { + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "attachment": true, + "reasoning": true, + "limit": { + "context": 1000000, + "output": 524288 + }, "cost": { "input": 0.3, "output": 1.2, @@ -99,6 +173,10 @@ "name": "MiniMax-M2.7", "attachment": false, "reasoning": true, + "limit": { + "context": 204800, + "output": 204800 + }, "cost": { "input": 0.3, "output": 1.2, diff --git a/tests/test_minimax_provider_config.py b/tests/test_minimax_provider_config.py index 20cb7af0..eaef6f90 100644 --- a/tests/test_minimax_provider_config.py +++ b/tests/test_minimax_provider_config.py @@ -14,6 +14,11 @@ "minimax-global-openai": "https://api.minimax.io/v1", "minimax-cn-openai": "https://api.minimaxi.com/v1", } +ANTHROPIC_PROVIDER = "minimax-anthropic" +MODEL_LIMITS = { + "MiniMax-M3": {"context": 1000000, "output": 524288}, + "MiniMax-M2.7": {"context": 204800, "output": 204800}, +} @pytest.fixture(scope="module") @@ -44,14 +49,33 @@ def test_minimax_openai_provider_registry( assert set(provider["models"]) == MODEL_IDS +def test_minimax_anthropic_provider_registry( + opencode_config: dict[str, object], +) -> None: + providers = opencode_config["provider"] + assert isinstance(providers, dict) + provider = providers[ANTHROPIC_PROVIDER] + + assert provider["npm"] == "@ai-sdk/anthropic" + assert provider["env"] == ["MINIMAX_API_KEY"] + assert provider["options"] == { + "apiKey": "{env:MINIMAX_API_KEY}", + "baseURL": "{env:ANTHROPIC_BASE_URL}/v1", + } + assert set(provider["models"]) == MODEL_IDS + + def test_minimax_model_metadata_matches_target_config( opencode_config: dict[str, object], ) -> None: providers = opencode_config["provider"] assert isinstance(providers, dict) - for provider_id in OPENAI_PROVIDERS: + for provider_id in (*OPENAI_PROVIDERS, ANTHROPIC_PROVIDER): models = providers[provider_id]["models"] + assert { + model_id: models[model_id]["limit"] for model_id in MODEL_IDS + } == MODEL_LIMITS assert models["MiniMax-M3"]["cost"] == { "input": 0.3, "output": 1.2, @@ -80,18 +104,24 @@ def test_minimax_anthropic_endpoints_and_model_ids_are_documented() -> None: for value in ( "https://api.minimax.io/anthropic", "https://api.minimaxi.com/anthropic", - "minimax/MiniMax-M3", - "minimax/MiniMax-M2.7", - "minimax-cn/MiniMax-M3", - "minimax-cn/MiniMax-M2.7", + "minimax-anthropic/MiniMax-M3", + "minimax-anthropic/MiniMax-M2.7", + "claude_code", "1,000,000", "204,800", "adaptive or disabled", "always on", - "$0.30 input / $1.20 output / $0.06 cache read", - "$0.30 input / $1.20 output / $0.06 cache read / $0.375 cache write", + "$0.30 / $1.20 / $0.06 / not charged", + "$0.60 / $2.40 / $0.12 / not charged", + "$0.45 / $1.80 / $0.09 / not charged", + "$0.90 / $3.60 / $0.18 / not charged", + "$0.30 / $1.20 / $0.06 / $0.375", ): assert value in docs + assert "minimax/MiniMax-M3" not in docs + assert "minimax/MiniMax-M2.7" not in docs + assert "minimax-cn/MiniMax-M3" not in docs + assert "minimax-cn/MiniMax-M2.7" not in docs assert "https://api.minimax.io/anthropic/v1" not in docs assert "https://api.minimaxi.com/anthropic/v1" not in docs