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
2 changes: 2 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The MiniMax provider guide link was added to the English README (README.en.md), but the Chinese README (README.md) was not updated. Please ensure both README files are kept in sync by adding the corresponding link to README.md as well.


> 💡 **Tip**: To change the port, edit the `ports` section in `docker-compose.yml`.

## 🖼️ Screenshots
Expand Down
51 changes: 51 additions & 0 deletions docs/providers/minimax.md
Original file line number Diff line number Diff line change
@@ -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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] [COMMENT-INACCURATE] The provider setup treats MiniMax-M3 as valid for the Anthropic-compatible entry.

Why this is a problem: MiniMax's Anthropic-compatible API currently documents the M2.x model family, while MiniMax-M3 is documented for the OpenAI-compatible text-generation API. Following this instruction creates a claude provider that allows MiniMax-M3, but requests sent to /anthropic/v1/messages can be rejected by the upstream service because that protocol does not support the model.

Suggested fix:

Create one `openai-compatible` provider entry per region for `MiniMax-M3`.
Create `claude` provider entries only for Anthropic-compatible models such as `MiniMax-M2.7`.
Set each provider's allowed model list to only the model IDs supported by that protocol.


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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include redirect source models in allowlists

With the setup described here, a provider allowlist containing only MiniMax-M3/MiniMax-M2.7 prevents redirect aliases from ever selecting the provider: providerSupportsModel checks the original requested model against allowedModels before redirects, and modelRedirects are applied only after provider selection (src/app/v1/_lib/proxy/provider-selector.ts:70-88). In any deployment that maps an application-specific name such as claude-sonnet -> MiniMax-M3, the MiniMax provider is filtered out as model_not_allowed, so this guidance should say to leave allowedModels empty or add the source alias/rule too.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] [COMMENT-INACCURATE] The redirect guidance conflicts with provider selection's allowlist order.

Why this is a problem: providerSupportsModel() checks allowedModels against the original requested model before provider selection, while ModelRedirector.apply() runs only after a provider has been selected. If an operator follows the preceding instruction and allows only MiniMax-M3/MiniMax-M2.7, a request for an application-specific alias is filtered out as model_not_allowed before its redirect to MiniMax can run.

Suggested fix:

If you route an application-specific alias through `modelRedirects`, either leave `allowedModels` empty or include both the source alias and the upstream MiniMax target model.

Example:
- allowed models: `claude-sonnet-4-5`, `MiniMax-M3`
- redirect rule: `claude-sonnet-4-5` -> `MiniMax-M3`

2 changes: 2 additions & 0 deletions src/lib/model-vendor-icons.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
2 changes: 2 additions & 0 deletions src/lib/model-vendor/vendor-inference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/app/v1/url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
Comment on lines +19 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 English test name in otherwise Chinese-named file

All surrounding tests in this file use Chinese names (e.g. "标准拼接:baseUrl 无路径时使用 requestPath + search", "避免重复拼接:…"), while the new test uses an English name. This creates a naming inconsistency. Additionally, the four sub-cases are bundled inside a single test() with a for…of loop; if one fails, Vitest only reports a single test failure without identifying which of the four cases was the culprit. The vendor-inference.test.ts sibling file uses it.each(cases) for the same kind of parametrized scenario, which gives one named result per case.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/app/v1/url.test.ts
Line: 19-46

Comment:
**English test name in otherwise Chinese-named file**

All surrounding tests in this file use Chinese names (e.g. `"标准拼接:baseUrl 无路径时使用 requestPath + search"`, `"避免重复拼接:…"`), while the new test uses an English name. This creates a naming inconsistency. Additionally, the four sub-cases are bundled inside a single `test()` with a `for…of` loop; if one fails, Vitest only reports a single test failure without identifying which of the four cases was the culprit. The `vendor-inference.test.ts` sibling file uses `it.each(cases)` for the same kind of parametrized scenario, which gives one named result per case.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


test("标准拼接:baseUrl 无路径时使用 requestPath + search", () => {
expectBuiltUrl(
"https://api.example.com",
Expand Down