diff --git a/README.en.md b/README.en.md index 11706cdbb..9b39f8e87 100644 --- a/README.en.md +++ b/README.en.md @@ -214,6 +214,8 @@ Once started: - **API Docs (Scalar UI)**: `http://localhost:23000/api/actions/scalar` - **API Docs (Swagger UI)**: `http://localhost:23000/api/actions/docs` +For a dual-region, dual-protocol provider setup, see the [MiniMax provider guide](docs/providers/minimax.md). + > 💡 **Tip**: To change the port, edit the `ports` section in `docker-compose.yml`. ## 🖼️ Screenshots diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md new file mode 100644 index 000000000..8a15cf248 --- /dev/null +++ b/docs/providers/minimax.md @@ -0,0 +1,51 @@ +# MiniMax Provider Guide + +Claude Code Hub can route MiniMax models through its existing Anthropic-compatible and OpenAI-compatible provider types. Create separate provider entries for each protocol and region so that routing and failover remain explicit. + +## Endpoint Matrix + +| Region | OpenAI-compatible base URL | Anthropic-compatible base URL | +| --- | --- | --- | +| Global | `https://api.minimax.io/v1` | `https://api.minimax.io/anthropic` | +| China | `https://api.minimaxi.com/v1` | `https://api.minimaxi.com/anthropic` | + +Use the URLs exactly as shown. The OpenAI-compatible URL includes `/v1`. The Anthropic-compatible URL ends at `/anthropic`; do not append `/v1`. Claude Code Hub appends the request path, including `/v1/messages`, when forwarding an Anthropic request. + +Official documentation: + +- Global: https://platform.minimax.io/docs/api-reference/api-overview +- China: https://platform.minimaxi.com/docs/api-reference/api-overview + +## Provider Entries + +Create one `claude` provider entry and one `openai-compatible` provider entry for each region. Set the provider URL to the matching value from the endpoint matrix and set the provider's allowed model list to the exact model IDs below. + +Do not reuse an OpenAI-compatible URL for an Anthropic provider entry or an Anthropic-compatible URL for an OpenAI-compatible entry. Keep the API key in the provider secret field and never place it in documentation or model redirect rules. + +## Model Catalog + +Prices are in USD per one million tokens. + +### MiniMax-M3 + +- Context window: `1,000,000` tokens +- Input modalities: text, image, and video +- Thinking: adaptive or disabled +- Input: `$0.60` +- Output: `$2.40` +- Cache read: `$0.12` +- Cache write pricing: not specified + +### MiniMax-M2.7 + +- Context window: `204,800` tokens +- Input modalities: text +- Thinking: always on +- Input: `$0.30` +- Output: `$1.20` +- Cache read: `$0.06` +- Cache write: `$0.375` + +## Request Routing + +Use `MiniMax-M3` or `MiniMax-M2.7` as the requested model ID. If the provider's allowed model list is configured, use exact-match entries for both IDs and keep both models available for routing. Existing model redirect rules can still map an application-specific name to either target model without changing the upstream provider URL. diff --git a/src/lib/model-vendor-icons.test.ts b/src/lib/model-vendor-icons.test.ts index b1b99775e..24576e789 100644 --- a/src/lib/model-vendor-icons.test.ts +++ b/src/lib/model-vendor-icons.test.ts @@ -19,6 +19,8 @@ describe("getModelVendor", () => { { modelId: "doubao-pro-32k", expectedVendor: "bytedance" }, { modelId: "glm-4-plus", expectedVendor: "zhipuai" }, { modelId: "kimi-k2", expectedVendor: "moonshotai" }, + { modelId: "MiniMax-M3", expectedVendor: "minimax" }, + { modelId: "MiniMax-M2.7", expectedVendor: "minimax" }, { modelId: "yi-lightning", expectedVendor: "01-ai" }, { modelId: "hunyuan-pro", expectedVendor: "tencent" }, { modelId: "ernie-4.0-8k", expectedVendor: "baidu" }, diff --git a/src/lib/model-vendor/vendor-inference.test.ts b/src/lib/model-vendor/vendor-inference.test.ts index 73bb7abb5..a91ffeddc 100644 --- a/src/lib/model-vendor/vendor-inference.test.ts +++ b/src/lib/model-vendor/vendor-inference.test.ts @@ -45,6 +45,8 @@ describe("inferVendorFromModelName", () => { { modelId: "glm-4-plus", expected: "zhipuai" }, { modelId: "minimax-pro", expected: "minimax" }, { modelId: "abab-6.5", expected: "minimax" }, + { modelId: "MiniMax-M3", expected: "minimax" }, + { modelId: "MiniMax-M2.7", expected: "minimax" }, { modelId: "kimi-k2", expected: "moonshotai" }, { modelId: "moonshot-v1-8k", expected: "moonshotai" }, { modelId: "yi-lightning", expected: "01-ai" }, diff --git a/tests/unit/app/v1/url.test.ts b/tests/unit/app/v1/url.test.ts index 0c06fbd79..c6a2256f5 100644 --- a/tests/unit/app/v1/url.test.ts +++ b/tests/unit/app/v1/url.test.ts @@ -16,6 +16,35 @@ function expectBuiltUrl(baseUrl: string, requestPath: string, expectedUrl: strin } describe("buildProxyUrl", () => { + test("preserves regional MiniMax protocol base paths", () => { + const cases = [ + { + baseUrl: "https://api.minimax.io/v1", + requestPath: "/v1/chat/completions", + expectedUrl: "https://api.minimax.io/v1/chat/completions", + }, + { + baseUrl: "https://api.minimaxi.com/v1", + requestPath: "/v1/chat/completions", + expectedUrl: "https://api.minimaxi.com/v1/chat/completions", + }, + { + baseUrl: "https://api.minimax.io/anthropic", + requestPath: "/v1/messages", + expectedUrl: "https://api.minimax.io/anthropic/v1/messages", + }, + { + baseUrl: "https://api.minimaxi.com/anthropic", + requestPath: "/v1/messages", + expectedUrl: "https://api.minimaxi.com/anthropic/v1/messages", + }, + ]; + + for (const { baseUrl, requestPath, expectedUrl } of cases) { + expectBuiltUrl(baseUrl, requestPath, expectedUrl); + } + }); + test("标准拼接:baseUrl 无路径时使用 requestPath + search", () => { expectBuiltUrl( "https://api.example.com",