-
-
Notifications
You must be signed in to change notification settings - Fork 3k
feat: add MiniMax as first-class provider with M2.7 models and native thinking support #2155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ var providerAppliers = map[string]ProviderApplier{ | |
| "iflow": nil, | ||
| "antigravity": nil, | ||
| "kimi": nil, | ||
| "minimax": nil, | ||
| } | ||
|
|
||
| // GetProviderApplier returns the ProviderApplier for the given provider name. | ||
|
|
@@ -96,7 +97,17 @@ func ApplyThinking(body []byte, model string, fromFormat string, toFormat string | |
| fromFormat = providerFormat | ||
| } | ||
| // 1. Route check: Get provider applier | ||
| // When the provider key has its own registered thinking applier (e.g., "minimax" | ||
| // via openai-compatibility), prefer it over the generic format applier (e.g., "openai"). | ||
| // This allows providers with custom thinking formats to work correctly when | ||
| // configured through openai-compatibility. | ||
| applier := GetProviderApplier(providerFormat) | ||
| if providerKey != providerFormat { | ||
| if keyApplier := GetProviderApplier(providerKey); keyApplier != nil { | ||
| applier = keyApplier | ||
| providerFormat = providerKey | ||
|
Comment on lines
+105
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When MiniMax is configured through the new Useful? React with 👍 / 👎. |
||
| } | ||
| } | ||
| if applier == nil { | ||
| log.WithFields(log.Fields{ | ||
| "provider": providerFormat, | ||
|
|
@@ -336,6 +347,12 @@ func extractThinkingConfig(body []byte, provider string) ThinkingConfig { | |
| case "kimi": | ||
| // Kimi uses OpenAI-compatible reasoning_effort format | ||
| return extractOpenAIConfig(body) | ||
| case "minimax": | ||
| config := extractMiniMaxConfig(body) | ||
| if hasThinkingConfig(config) { | ||
| return config | ||
| } | ||
| return extractOpenAIConfig(body) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The MiniMax branch only falls back to Useful? React with 👍 / 👎. |
||
| default: | ||
| return ThinkingConfig{} | ||
| } | ||
|
|
@@ -495,6 +512,23 @@ func extractCodexConfig(body []byte) ThinkingConfig { | |
| return ThinkingConfig{} | ||
| } | ||
|
|
||
| // extractMiniMaxConfig extracts thinking configuration from MiniMax format request body. | ||
| // | ||
| // MiniMax API format: | ||
| // - reasoning_split: boolean (true to enable, false to disable) | ||
| // | ||
| // Returns ModeBudget with Budget=1 as a sentinel value indicating "enabled". | ||
| // Budget=1 is used because MiniMax models don't use numeric budgets; they only support on/off. | ||
| func extractMiniMaxConfig(body []byte) ThinkingConfig { | ||
| if split := gjson.GetBytes(body, "reasoning_split"); split.Exists() { | ||
| if split.Bool() { | ||
| return ThinkingConfig{Mode: ModeBudget, Budget: 1} | ||
| } | ||
| return ThinkingConfig{Mode: ModeNone, Budget: 0} | ||
| } | ||
| return ThinkingConfig{} | ||
| } | ||
|
|
||
| // extractIFlowConfig extracts thinking configuration from iFlow format request body. | ||
| // | ||
| // iFlow API format (supports multiple model families): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| // Package minimax implements thinking configuration for MiniMax models. | ||
| // | ||
| // MiniMax models use a boolean toggle for thinking: | ||
| // - reasoning_split: true/false | ||
| // | ||
| // Level values are converted to boolean: none=false, all others=true | ||
| package minimax | ||
|
|
||
| import ( | ||
| "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" | ||
| "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" | ||
| "github.com/tidwall/gjson" | ||
| "github.com/tidwall/sjson" | ||
| ) | ||
|
|
||
| // Applier implements thinking.ProviderApplier for MiniMax models. | ||
| // | ||
| // MiniMax-specific behavior: | ||
| // - Uses reasoning_split boolean toggle | ||
| // - Level to boolean: none=false, others=true | ||
| // - No quantized support (only on/off) | ||
| type Applier struct{} | ||
|
|
||
| var _ thinking.ProviderApplier = (*Applier)(nil) | ||
|
|
||
| // NewApplier creates a new MiniMax thinking applier. | ||
| func NewApplier() *Applier { | ||
| return &Applier{} | ||
| } | ||
|
|
||
| func init() { | ||
| thinking.RegisterProvider("minimax", NewApplier()) | ||
| } | ||
|
|
||
| // Apply applies thinking configuration to MiniMax request body. | ||
| // | ||
| // Expected output format: | ||
| // | ||
| // { | ||
| // "reasoning_split": true/false | ||
| // } | ||
| func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) { | ||
| if !thinking.IsUserDefinedModel(modelInfo) && modelInfo.Thinking == nil { | ||
| return body, nil | ||
| } | ||
| return applyMiniMax(body, config), nil | ||
| } | ||
|
|
||
| // configToBoolean converts ThinkingConfig to boolean for MiniMax models. | ||
| // | ||
| // Conversion rules: | ||
| // - ModeNone: false | ||
| // - ModeAuto: true | ||
| // - ModeBudget + Budget=0: false | ||
| // - ModeBudget + Budget>0: true | ||
| // - ModeLevel + Level="none": false | ||
| // - ModeLevel + any other level: true | ||
| // - Default (unknown mode): true | ||
| func configToBoolean(config thinking.ThinkingConfig) bool { | ||
| switch config.Mode { | ||
| case thinking.ModeNone: | ||
| return false | ||
| case thinking.ModeAuto: | ||
| return true | ||
| case thinking.ModeBudget: | ||
| return config.Budget > 0 | ||
| case thinking.ModeLevel: | ||
| return config.Level != thinking.LevelNone | ||
| default: | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| // applyMiniMax applies thinking configuration for MiniMax models. | ||
| // | ||
| // Output format: | ||
| // | ||
| // {"reasoning_split": true/false} | ||
| func applyMiniMax(body []byte, config thinking.ThinkingConfig) []byte { | ||
| reasoningSplit := configToBoolean(config) | ||
|
|
||
| if len(body) == 0 || !gjson.ValidBytes(body) { | ||
| body = []byte(`{}`) | ||
| } | ||
|
|
||
| // Remove any OpenAI-style reasoning_effort that may have been set | ||
| body, _ = sjson.DeleteBytes(body, "reasoning_effort") | ||
| body, _ = sjson.SetBytes(body, "reasoning_split", reasoningSplit) | ||
|
Comment on lines
+86
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
|
|
||
| return body | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a
minimaxsection inmodels.jsonis not sufficient becauseinternal/registry/model_definitions.gostill does not define or traverse aminimaxfield instaticModelsJSON,GetStaticModelDefinitionsByChannel, orLookupStaticModelInfo; the new entries are therefore dropped on unmarshal and never found by lookup. In practice,LookupModelInfo(model, "minimax")returns nil for these models, so MiniMax requests are treated as user-defined and bypass the capability metadata/validation this commit is trying to introduce.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch! Fixed in 3e4482c — added
MiniMaxfield tostaticModelsJSON,GetMiniMaxModels()getter,case "minimax"inGetStaticModelDefinitionsByChannel, anddata.MiniMaxtoLookupStaticModelInfo.