// Package minimax 实现 MiniMax 官方 API 的 ModelProvider.
//
// MiniMax 提供三种 API 接入方式:
// 1. Native API - /v1/text/chatcompletion_v2(官方路径,本实现默认)
// 2. OpenAI 兼容 - /v1/chat/completions
// 3. Anthropic 兼容 - /anthropic/v1/messages(MiniMax 对外推广,但实测效果不稳定)
//
// 默认使用 Native 模式,原因:
// - Native 是 MiniMax 的第一方 API,语义最完整
// - 实测 Anthropic 兼容端点在编程类 Agent 场景下表现较差
// (可能是 Anthropic 特有的 beta headers / system prompt 格式未完全兼容)
// - OpenAI 兼容和 Native 的 SSE 格式相同,切换成本低
//
// Native API 的 SSE 格式与 OpenAI 完全相同(data: {...}, data: [DONE]),
// 因此我们复用 internal/wire/openai_compat.go,只需修改端点路径.
//
// 历史包袱(LEGACY): flysafe 早期通过 BaseURL="https://api.minimaxi.com/anthropic"
// 使用 MiniMax,走的是 Anthropic 兼容端点.
// 迁移路径:flysafe 改用 minimax.New(Config{APIKey: "..."}) 即可,
// 无需关心底层使用哪种格式.
package minimax
import (
"context"
"fmt"
"net/http"
"time"
"git.flytoex.net/yuanwei/flyto-agent/core/internal/transport"
"git.flytoex.net/yuanwei/flyto-agent/core/internal/transport/retry"
"git.flytoex.net/yuanwei/flyto-agent/core/internal/wire"
"git.flytoex.net/yuanwei/flyto-agent/core/pkg/flyto"
"git.flytoex.net/yuanwei/flyto-agent/core/pkg/providers/shared"
)
// Mode 是 MiniMax API 接入模式.
type Mode string
const (
// ModeNative 使用 MiniMax 官方 API(/v1/text/chatcompletion_v2).
// 推荐:语义最完整,SSE 格式与 OpenAI 相同,思考内容通过 delta.reasoning 字段传递.
ModeNative Mode = "native"
// ModeOpenAI 使用 MiniMax 的 OpenAI 兼容端点(/v1/chat/completions).
// 与 ModeNative 几乎等价,端点路径不同.
ModeOpenAI Mode = "openai"
// ModeAnthropic 使用 MiniMax 的 Anthropic 兼容端点(/anthropic/v1/messages).
// 注意:实测在编程类 Agent 场景下效果可能不如 Native.
// 仅在明确需要 Anthropic 格式特性(如 cache_control 细粒度控制)时使用.
ModeAnthropic Mode = "anthropic"
)
// Region 是 MiniMax API 地区.
type Region string
const (
// RegionGlobal 使用国际节点(api.minimax.io).
RegionGlobal Region = "global"
// RegionChina 使用中国节点(api.minimaxi.com).
RegionChina Region = "china"
)
// Config 是 MiniMax provider 的配置.
type Config struct {
// APIKey 是 MiniMax API 密钥(从 platform.minimax.io 获取).
APIKey string
// Region 控制使用国际节点还是中国节点(默认 RegionChina).
// MiniMax 主要面向中国市场,默认使用国内节点(api.minimaxi.com).
// 如需使用国际节点(api.minimax.io),显式设置 RegionGlobal.
Region Region
// Mode 控制使用哪种 API 格式(默认 ModeNative).
Mode Mode
// ThinkingBudget 启用思考模式(仅 M2.1+ 支持).
// 0 = 禁用.推荐值:5000-16000.
// ModeNative/ModeOpenAI 时通过 reasoning_split 参数传递.
// ModeAnthropic 时通过 thinking.budget_tokens 传递.
ThinkingBudget int
// HTTPClient 注入自定义 HTTP 客户端.
// nil = 使用默认 http.Client (带 Timeout 字段配置的 ResponseHeaderTimeout).
// 非 nil 时 consumer 完全接管超时责任, 下面的 Timeout 字段被忽略.
//
// 精妙之处(CLEVER): minimax 的 HTTPClient 会被双模式 (ModeAnthropic / ModeNative|OpenAI)
// 两个独立客户端同时使用 - 消费者注入的 client 会被两边共享.这是 MiniMax 特有的,
// 因为同一个 Provider 实例只会选一种 Mode, 不会同时用两个 client.
HTTPClient *http.Client
// Timeout 限制"从请求发出到收到响应首字节"的时间.
//
// 通过 http.Transport.ResponseHeaderTimeout 实现, **不影响** SSE 流式响应的后续
// body 读取 - MiniMax 长思考模式 (M2.7 Thinking) 可以正常读完.
//
// 精妙之处(CLEVER): 不要误用 http.Client.Timeout - 那会把 SSE 流砍死.
// MiniMax 的双模式 (ModeAnthropic / ModeNative|OpenAI) 都从这一个字段读超时,
// 两个底层 client 独立应用, 语义保持等价.
//
// 0 = 使用 defaultTimeout (60s, 适合 MiniMax 云端).
// 仅当 HTTPClient 为 nil 时生效; 提供自定义 HTTPClient 时此字段被忽略.
Timeout time.Duration
// ModelOverrides 覆盖静态模型表.
ModelOverrides []flyto.ModelInfo
}
// defaultTimeout 是 Config.Timeout == 0 且 Config.HTTPClient == nil 时的 ResponseHeaderTimeout 兜底值.
// 60s 对 MiniMax 云端 (国内 api.minimaxi.com / 国际 api.minimax.io) 合理.
// M2.7 Thinking 模式可能在开始响应前思考数秒, 但 60s 仍充足.
const defaultTimeout = 60 * time.Second
// GoString 实现 fmt.GoStringer,防止 %#v 打印时泄露 APIKey.
// 升华改进(ELEVATED): 参见 anthropic.Config.GoString 的说明,同理.
func (c Config) GoString() string {
return shared.GoStringWithMaskedKey("Config", c.APIKey)
}
// Provider 是 MiniMax ModelProvider 实现.
type Provider struct {
cfg Config
baseURL string
wireClient *wire.OpenAICompatClient // Native/OpenAI 模式
anthroClient *api.Client // Anthropic 兼容模式
}
// New 创建 MiniMax ModelProvider.
func New(cfg Config) *Provider {
if cfg.Region == "" {
cfg.Region = RegionChina
}
if cfg.Mode == "" {
cfg.Mode = ModeNative
}
var baseURL string
switch cfg.Region {
case RegionChina:
baseURL = "https://api.minimaxi.com"
default:
baseURL = "https://api.minimax.io"
}
p := &Provider{cfg: cfg, baseURL: baseURL}
switch cfg.Mode {
case ModeAnthropic:
// Anthropic 兼容模式:复用 internal/transport(package api),BearerAuth=true.
//
// 升华改进(ELEVATED): 2026-04-11 - 补齐与 anthropic.New() 构造链对等的 4 个
// robustness option.此前 ModeAnthropic 只注入 WithMessagePath + WithBearerAuth,
// 缺少 WithAPIVersion / WithClassifier / WithRetryPolicy / WithOverflowHandler,
// 导致错误分类,429/529 重试,max_tokens 溢出修正器全部缺失.
// 成功路径不受影响(curl + Go 端到端 integration test 已双重验证),
// 但错误/重试/异常路径的行为不如 anthropic.New() 健壮.
//
// 替代方案:<保持最小构造,让消费者自己 WithHTTPClient(custom)> - 否决:
// 把 Anthropic 协议特有的选项(比如 AnthropicClassifier)推给消费者违反
// "provider 内聚 protocol 细节" 原则.消费者不需要知道 Anthropic 兼容端点的
// retry/overflow 细节.
//
// 反向思维:WithAPIVersion 要不要?minimax 端实测不要求此 header(不带也返回 200),
// 但 anthropic.New() 默认会注入.为了 ModeAnthropic 与 anthropic.New() 两条路径
// 的行为完全对等(便于 flysafe 类消费者从 BaseURL hack 迁移),保留注入.
var opts []api.ClientOption
opts = append(opts,
api.WithMessagePath("/v1/messages"),
api.WithAPIVersion("2023-06-01"),
api.WithClassifier(&api.AnthropicClassifier{Hinter: &api.DefaultHinter{}}),
api.WithRetryPolicy(retry.NewAnthropicRetryPolicy(retry.AnthropicRetryOpts{})),
api.WithOverflowHandler(retry.DefaultOverflowHandler()),
)
// 超时配置: HTTPClient 和 Timeout 二选一, 不混用 (见 Config.Timeout 注释).
if cfg.HTTPClient != nil {
opts = append(opts, api.WithHTTPClient(cfg.HTTPClient))
} else {
timeout := cfg.Timeout
if timeout == 0 {
timeout = defaultTimeout
}
opts = append(opts, api.WithResponseHeaderTimeout(timeout))
}
opts = append(opts, api.WithBearerAuth())
p.anthroClient = api.NewClient(cfg.APIKey, baseURL+"/anthropic", opts...)
default:
// Native / OpenAI 兼容模式:使用 wire.OpenAICompatClient
wireOpts := []wire.OpenAICompatOption{}
// 超时配置: 与 ModeAnthropic 分支语义对称,只是走 wire 包的 option.
if cfg.HTTPClient != nil {
wireOpts = append(wireOpts, wire.WithHTTPClient(cfg.HTTPClient))
} else {
timeout := cfg.Timeout
if timeout == 0 {
timeout = defaultTimeout
}
wireOpts = append(wireOpts, wire.WithResponseHeaderTimeout(timeout))
}
if cfg.Mode == ModeNative {
// Native API 使用不同的端点路径
wireOpts = append(wireOpts, wire.WithChatPath("/v1/text/chatcompletion_v2"))
}
p.wireClient = wire.NewOpenAICompatClient(cfg.APIKey, baseURL, wireOpts...)
}
return p
}
var _ flyto.ModelProvider = (*Provider)(nil)
// Name 返回 provider 标识.
func (p *Provider) Name() string { return "minimax" }
// Stream 向 MiniMax API 发起流式请求.
//
// 升华改进(ELEVATED): data-driven-capabilities RFC PR2.6 - Stream dispatcher 层统一处理
// max_tools 检查 + want×can 检测.两条 stream path(streamOpenAI / streamAnthropic)原本
// 各自做一份检查,提到 dispatcher 层 DRY + 行为一致.能力决策是模型级属性,和序列化格式无关.
func (p *Provider) Stream(ctx context.Context, req *flyto.Request) (<-chan flyto.Event, error) {
// CAP-7: Tool 数量上限检查 - 统一入口,两条路径共用.
// 兜底 minimaxMaxTools=0 跳过检查(probe 未发现上限);registry 可注入实测上限.
// 仅 Exhaustive=true 时客户端硬拒.
maxTools, exhaustive := resolveMaxTools(req)
if maxTools > 0 && exhaustive && len(req.Tools) > maxTools {
if err := wire.CheckToolCount(req.Tools, maxTools); err != nil {
return nil, err
}
}
// 双开关协议(want × can)检测 - 两条 path 共用同一份 warning.
// 模型是否支持 thinking 是模型级属性,和序列化格式无关.
warnings := p.detectFeatureWarnings(req)
var ch <-chan flyto.Event
var err error
switch p.cfg.Mode {
case ModeAnthropic:
ch, err = p.streamAnthropic(ctx, req)
default:
ch, err = p.streamOpenAI(ctx, req)
}
if err != nil {
return nil, err
}
if len(warnings) > 0 {
return prependWarnings(ch, warnings), nil
}
return ch, nil
}
// streamOpenAI 通过 Native 或 OpenAI 兼容端点流式请求.
// 注:max_tools 检查 + want×can 检测已在 Stream dispatcher 层完成(PR2.6).
func (p *Provider) streamOpenAI(ctx context.Context, req *flyto.Request) (<-chan flyto.Event, error) {
// CAP-5: $ref 展开--MiniMax 直连有 $ref 双重序列化 bug.
// 展开后 tool input 中被引用字段为 object 而非 JSON 字符串,避免静默数据损坏.
// 错误时 silently skip(保留原始 schema)而非报错:
// 1. 循环引用/外部 $ref 等异常 schema 应让 provider 返回明确错误,而非引擎拦截;
// 2. 绝大多数工具 schema 无 $ref,快速路径保证零开销.
tools := make([]flyto.Tool, len(req.Tools))
copy(tools, req.Tools)
for i, t := range tools {
expanded, err := wire.DereferenceSchema(t.InputSchema)
if err == nil {
tools[i].InputSchema = expanded
}
// err != nil: silently skip,保留原始 schema,让 provider 决定如何处理
tools[i].InputSchema = wire.AdaptSchema(tools[i].InputSchema, "openai")
}
wireReq := &wire.StreamRequest{
Model: req.Model,
Messages: req.Messages,
System: req.System,
MaxTokens: req.MaxTokens,
Tools: tools,
ResponseFormat: req.ResponseFormat,
Temperature: req.Temperature,
TopP: req.TopP,
TopK: req.TopK,
MinP: req.MinP,
}
// MiniMax M2.1+ 支持思考模式
// Native/OpenAI 模式下通过 reasoning 参数开启(与 OpenRouter 相同字段)
// NeedsThinking 支持 per-request 触发,ThinkingBudget=0 时 MaxTokens 取 Config 值(0=provider 决定).
if p.cfg.ThinkingBudget > 0 || req.NeedsThinking {
wireReq.Reasoning = &wire.Reasoning{
MaxTokens: p.cfg.ThinkingBudget,
Enabled: true,
}
}
// reasoning_split (ModeOpenAI only): MiniMax M2.1+ reasoning models (M3 /
// M2.7-highspeed) ALWAYS emit reasoning, and the OpenAI-compat endpoint
// inlines it as a ... block inside content by default. That
// pollutes a consumer's first-brace-to-last-brace JSON extraction whenever
// the reasoning text contains a brace (frequent when the model reasons
// about the JSON it is about to emit). reasoning_split: true tells MiniMax
// to put reasoning on reasoning_content / reasoning_details instead;
// wire.ParseOpenAIStream already routes that channel to ThinkingDeltaEvent,
// so content stays clean and the consumer never parses the marker format
// (engine-layer reasoning-normalization contract -- the consumer is
// unaware). Set unconditionally for ModeOpenAI (these models always
// reason). The native endpoint (/v1/text/chatcompletion_v2) is left
// untouched: reasoning_split is documented for the OpenAI-compat path and
// not verified on native, so a native caller keeps the default behavior.
//
// reasoning_split (仅 ModeOpenAI): MiniMax M2.1+ reasoning 模型 (M3 /
// M2.7-highspeed) 永远吐 reasoning, OpenAI 兼容端点默认内联成 content 里的
// ... 块. 一旦 reasoning 文本含花括号 (模型推理它即将输出的
// JSON 时很常见) 就污染消费者的 first-brace-to-last-brace JSON 抽取.
// reasoning_split: true 让 MiniMax 改放 reasoning_content / reasoning_details;
// wire.ParseOpenAIStream 已把这条通道路由成 ThinkingDeltaEvent, content 保持
// 干净, 消费者永不解析 marker 格式 (引擎层 reasoning 归一契约 -- 消费者无感).
// ModeOpenAI 无条件设 (这些模型永远 reason). Native 端点
// (/v1/text/chatcompletion_v2) 不动: reasoning_split 是 OpenAI-compat 路径的
// 文档参数, native 上未实测, 故 native 调用方保持默认行为.
if p.cfg.Mode == ModeOpenAI {
split := true
wireReq.ReasoningSplit = &split
}
return p.wireClient.Stream(ctx, wireReq)
}
// streamAnthropic 通过 Anthropic 兼容端点流式请求(复用 internal/api/client.go).
func (p *Provider) streamAnthropic(ctx context.Context, req *flyto.Request) (<-chan flyto.Event, error) {
// 历史包袱(LEGACY): Anthropic 兼容模式需要将 flyto.Request 转换为 api.MessageRequest.
// 与 pkg/providers/anthropic 中的逻辑相同,但不需要处理 Anthropic 专有的 Beta headers.
// 如果 MiniMax Anthropic 兼容层完全对齐 Anthropic API,可以复用 anthropic.Provider 的逻辑.
// 暂时单独实现以保持独立性,避免 minimax 包依赖 anthropic 包(引入不必要的耦合).
apiReq := &api.MessageRequest{
Model: req.Model,
MaxTokens: req.MaxTokens,
Stream: true,
// MiniMax Anthropic 兼容路径直接透传 Temperature / TopP. MiniMax 文档
// 未提及与 thinking 模式的强约束 (与 Anthropic 原生 thinking 不同),
// 不做 client-side override; 越界由服务端校验.
//
// MiniMax Anthropic-compat path passes Temperature / TopP through.
// MiniMax docs do not document thinking-vs-temperature constraints
// (unlike native Anthropic), so no client-side override; out-of-range
// surfaces from server.
Temperature: req.Temperature,
TopP: req.TopP,
}
if req.System != "" {
apiReq.SetSystemString(req.System)
}
budget := p.cfg.ThinkingBudget
if budget == 0 && req.NeedsThinking {
budget = 8000 // 与 anthropic provider 保持一致的默认值
}
if budget > 0 {
apiReq.Thinking = &api.ThinkingConfig{
Type: "enabled",
BudgetTokens: budget,
}
}
for _, msg := range req.Messages {
role := string(msg.Role)
if len(msg.Blocks) == 1 && msg.Blocks[0].Type == flyto.BlockText {
apiReq.Messages = append(apiReq.Messages, api.NewTextMessage(role, msg.Blocks[0].Text))
continue
}
var blocks []api.ContentBlock
for _, b := range msg.Blocks {
switch b.Type {
case flyto.BlockText:
blocks = append(blocks, api.ContentBlock{Type: "text", Text: b.Text})
case flyto.BlockToolUse:
blocks = append(blocks, api.ContentBlock{
Type: "tool_use",
ID: b.ToolUseID,
Name: b.ToolName,
Input: b.ToolInput,
})
case flyto.BlockToolResult:
// MiniMax anthropic-compat 端点对 tool_result array-form 接受
// 未验证; 保守拒绝 ResultBlocks 形式, 让调用方切到 anthropic
// provider 做 vision. 纯文本 tool_result 继续走 string path.
//
// Whether MiniMax anthropic-compat accepts array-form
// tool_result content is unverified; conservatively reject
// ResultBlocks form and route callers to the anthropic
// provider for vision. Plain-text tool_result stays on the
// string path.
if len(b.ResultBlocks) > 0 {
return nil, fmt.Errorf("minimax: tool_result with ResultBlocks not yet supported (use anthropic provider for vision)")
}
blocks = append(blocks, api.ContentBlock{
Type: "tool_result",
ToolUseID: b.ToolUseID,
Content: b.ResultText,
IsError: b.IsError,
})
case flyto.BlockImage:
// Vision not yet wired for MiniMax anthropic-compat endpoint:
// native spec support unverified, per-provider enablement
// deferred. Surface as error instead of silent-dropping the
// image so callers learn to select a vision-capable provider.
//
// MiniMax anthropic-compat 端点的 vision 支持未接: 兼容 spec
// 是否真的接受 image block 未验证, 按 provider 逐个启用的节奏
// 延后. 返 error 不静默丢图, 调用方据此切换到 vision-capable
// provider.
return nil, fmt.Errorf("minimax: BlockImage not yet supported (use anthropic provider for vision)")
}
}
apiReq.Messages = append(apiReq.Messages, api.NewBlockMessage(role, blocks))
}
// 升华改进(ELEVATED): streamOpenAI 路径已做 DereferenceSchema + schema 适配,
// Anthropic 兼容模式同样需要--MiniMax 有 $ref 双重序列化 bug,
// 且 Anthropic API 不支持 additionalProperties 等非标准字段.
// 替代方案:<直接透传 t.InputSchema> - 否决:已有实测证明会导致 400 错误.
for _, t := range req.Tools {
schema := t.InputSchema
if expanded, err := wire.DereferenceSchema(schema); err == nil {
schema = expanded
}
schema = wire.AdaptSchema(schema, "anthropic")
apiReq.Tools = append(apiReq.Tools, api.ToolDef{
Name: t.Name,
Description: t.Description,
InputSchema: schema,
})
}
// 升华改进(ELEVATED): 早期方案需要 convertAnthropicStream goroutine 做中间转换--
// 现在 wire.ParseAnthropicStream + StreamGuard 在 client.CreateMessageStream 内部
// 直接产出 flyto.Event,无需额外转换层.
return p.anthroClient.CreateMessageStream(ctx, apiReq)
}
// Models 返回 MiniMax 可用模型列表(静态表).
func (p *Provider) Models(_ context.Context) ([]flyto.ModelInfo, error) {
if len(p.cfg.ModelOverrides) > 0 {
return p.cfg.ModelOverrides, nil
}
return minimaxModels, nil
}
// minimaxMaxTools 是 MiniMax 工具数量上限的**兜底安全值**(0 = probe 未发现上限).
// probe 实测 @256 工具未触发 API 报错(2026-04).
//
// 升华改进(ELEVATED): data-driven-capabilities RFC PR2.6 - 主决策来自
// req.Capabilities.MaxTools.本常量仅在 Capabilities == nil 时兜底.
// 当 probe 确认上限后可通过 capability-probe 写入 registry,无需改本包代码.
const minimaxMaxTools = 0
// resolveMaxTools 返回当前请求的工具上限 + 是否为硬性上限(Exhaustive).
// registry 优先 + 包内 minimaxMaxTools 兜底.
//
// 零回归:nil Capabilities → (0, false) → dispatcher 不进入 check 分支,等同 PR2.6 之前.
func resolveMaxTools(req *flyto.Request) (int, bool) {
if req.Capabilities != nil && req.Capabilities.MaxTools > 0 {
return req.Capabilities.MaxTools, req.Capabilities.MaxToolsExhaustive
}
return minimaxMaxTools, false
}
// modelSupportsThinkingFallback 扫 minimax 静态表判断模型是否支持 thinking.
// M3/M2.7/M2.7-highspeed/M2.5/M2.1 返回 true,M2/M1 返回 false,未知模型保守返回 false.
// 数据驱动: 读静态表的 SupportsThinking, 加新模型只需在表里设 flag.
func modelSupportsThinkingFallback(model string) bool {
for _, m := range minimaxModels {
if m.ID == model {
return m.SupportsThinking
}
}
return false
}
// resolveThinkingSupport 返回当前请求模型是否支持 thinking.registry 优先 + 静态表兜底.
func resolveThinkingSupport(req *flyto.Request) bool {
if req.Capabilities != nil {
return req.Capabilities.SupportsThinking
}
return modelSupportsThinkingFallback(req.Model)
}
// detectFeatureWarnings 检测 want × can 不一致的场景,返回 WarningEvent 列表.
//
// 升华改进(ELEVATED): data-driven-capabilities RFC §4.4 双开关协议.
// minimax 两条 stream 路径(streamOpenAI / streamAnthropic)原本都**无条件**注入 thinking
// 参数,不检查模型是否支持.PR2.6 保留无条件注入(零回归),但通过 WarningEvent 检测
// user opt-in 但模型不支持的场景,让 silent disable 变 loud.
//
// minimax 两条路径共用同一份 warning - 因为"模型是否支持 thinking"是模型级属性,和
// 序列化格式(OpenAI vs Anthropic)无关.因此本 helper 放在 Stream dispatcher 层,
// 避免两条 path 重复调用.
//
// minimax 当前仅检测 thinking;Config 无 EnableCaching 字段(caching 在 wire 层自动处理),
// 所以 caching 的 want 永远为 false,不触发检测.
func (p *Provider) detectFeatureWarnings(req *flyto.Request) []*flyto.WarningEvent {
var warnings []*flyto.WarningEvent
wantsThinking := p.cfg.ThinkingBudget > 0 || req.NeedsThinking
if wantsThinking && !resolveThinkingSupport(req) {
warnings = append(warnings, &flyto.WarningEvent{
Code: "feature_unsupported",
Message: "Config.ThinkingBudget/NeedsThinking 已设置但模型 " + req.Model + " 不支持 thinking,thinking 可能被 API 忽略",
Detail: "model=" + req.Model + " feature=thinking",
})
}
return warnings
}
// prependWarnings 在下游 channel 前面 prepend 一组 WarningEvent.
// 参见 anthropic/ollama/openai/gemini/openrouter 同款实现;RFC §3 决策不抽共享包.
func prependWarnings(downstream <-chan flyto.Event, warnings []*flyto.WarningEvent) <-chan flyto.Event {
out := make(chan flyto.Event, len(warnings))
for _, w := range warnings {
out <- w
}
go func() {
defer close(out)
for evt := range downstream {
out <- evt
}
}()
return out
}
// minimaxModels 是 MiniMax 已知模型的静态表(2026-06 更新).
// 上下文窗口 200K(MiniMax-M3 / M1 为 1M),无 batch API.
var minimaxModels = []flyto.ModelInfo{
{
// MiniMax-M3 (launched 2026-06-01): frontier coding / agent model,
// native multimodal (text + image + video in, text out), 1M context
// via MiniMax Sparse Attention (MSA, ~1/20 per-token compute at 1M vs
// the prior gen). Context / max-output / pricing are the official
// launch specs; the capability flags (thinking / tools / caching)
// mirror M2.7 pending a capability-probe run on M3.
//
// MiniMax-M3 (2026-06-01 发布): 前沿 coding / agent 模型, 原生多模态
// (text + image + video 入, text 出), 经 MiniMax 稀疏注意力 (MSA) 达
// 1M 上下文 (1M 时 per-token 算力约上代 1/20). context / max-output /
// 定价用官方 launch 真值; 能力 flag (thinking / tools / caching) 镜像
// M2.7, 待 capability-probe 实测 M3 后调整.
ID: "MiniMax-M3",
DisplayName: "MiniMax M3",
Provider: "minimax",
ToolNameRegex: `^[a-zA-Z0-9_-]+$`,
ReasoningPassbackMode: "",
ProviderKind: "direct",
ContextWindow: 1_048_576,
MaxOutputTokens: 512_000,
// List price per the 2026-06-01 launch (USD / 1M tokens); a 50% promo
// (-> 0.30 / 1.20) is active at launch. Static table holds list price
// (discount reconciled downstream), matching the deepseek convention.
// Multimodal image/video input bills at 1.00 / 1M (not modeled here --
// the text in/out rates cover the dispatch's text-only use).
//
// 定价取 2026-06-01 launch (USD / 1M tokens); launch 期有 50% promo
// (-> 0.30 / 1.20). 静态表填 list 原价 (折扣下游对账), 同 deepseek 约定.
// 多模态 image/video 入按 1.00 / 1M 计费 (此处不建模 -- text 入出价覆盖
// dispatch 的纯文本用途).
InputPricePer1M: 0.60,
OutputPricePer1M: 2.40,
SupportsCaching: true,
SupportsThinking: true,
SupportsVision: true,
CachingMinTokens: 1024,
MaxTools: 0,
},
{
ID: "MiniMax-M2.7",
DisplayName: "MiniMax M2.7",
Provider: "minimax",
// ADR-0007: MiniMax OpenAI-compat 路径 ^[a-zA-Z0-9_-]+$.
// reasoning passback 暂留 "" (未实测; r3-r10 实证 minimax 主路径
// 不走 thinking 模式, 等真消费者驱动再补). ProviderKind=direct.
ToolNameRegex: `^[a-zA-Z0-9_-]+$`,
ReasoningPassbackMode: "",
ProviderKind: "direct",
ContextWindow: 205_000,
MaxOutputTokens: 16_000,
InputPricePer1M: 0.30,
OutputPricePer1M: 1.20,
SupportsCaching: true,
SupportsThinking: true,
SupportsVision: true,
// probe 实测:cr=1110 rd=1110 @ 100 reps(~1000t 系统提示),阈值 ≤ 1024.
CachingMinTokens: 1024,
MaxTools: 0, // probe 实测 @256 未发现上限(2026-04),待后续验证
},
{
// 升华改进(ELEVATED): M2.7-highspeed 是 M2.7 的快速变体,2026-04 capability-probe
// 实测能力与 M2.7 完全一致(streaming/thinking/tool_use/structured_out/caching cr=1110 rd=1110/schema_ref/tool_count≥128).
// 静态表镜像 M2.7 的所有字段,仅 ID/DisplayName 不同;如官方公布 highspeed 专属定价或上下文差异,再单独调整.
// 替代方案:<不收录,等用户显式 New(ModelOverrides=...) 注入> - 否决:probe 已主动测试此 ID,
// Models() 返回结果若不包含此变体,定价/进度条等下游消费者会无法识别,造成 UI 断码.
ID: "MiniMax-M2.7-highspeed",
DisplayName: "MiniMax M2.7 (Highspeed)",
Provider: "minimax",
// ADR-0007: MiniMax OpenAI-compat 路径 ^[a-zA-Z0-9_-]+$.
// reasoning passback 暂留 "" (未实测; r3-r10 实证 minimax 主路径
// 不走 thinking 模式, 等真消费者驱动再补). ProviderKind=direct.
ToolNameRegex: `^[a-zA-Z0-9_-]+$`,
ReasoningPassbackMode: "",
ProviderKind: "direct",
ContextWindow: 205_000,
MaxOutputTokens: 16_000,
InputPricePer1M: 0.30,
OutputPricePer1M: 1.20,
SupportsCaching: true,
SupportsThinking: true,
SupportsVision: true,
CachingMinTokens: 1024,
MaxTools: 0,
},
{
ID: "MiniMax-M2.5",
DisplayName: "MiniMax M2.5",
Provider: "minimax",
// ADR-0007: MiniMax OpenAI-compat 路径 ^[a-zA-Z0-9_-]+$.
// reasoning passback 暂留 "" (未实测; r3-r10 实证 minimax 主路径
// 不走 thinking 模式, 等真消费者驱动再补). ProviderKind=direct.
ToolNameRegex: `^[a-zA-Z0-9_-]+$`,
ReasoningPassbackMode: "",
ProviderKind: "direct",
ContextWindow: 200_000,
MaxOutputTokens: 16_000,
InputPricePer1M: 0.118,
OutputPricePer1M: 0.95,
SupportsCaching: true,
SupportsThinking: true,
SupportsVision: true,
CachingMinTokens: 1024, // 与 M2.7 同系列,预期阈值相同;未单独 probe
MaxTools: 0, // probe 实测 @256 未发现上限(2026-04)
},
{
ID: "MiniMax-M2.1",
DisplayName: "MiniMax M2.1",
Provider: "minimax",
// ADR-0007: MiniMax OpenAI-compat 路径 ^[a-zA-Z0-9_-]+$.
// reasoning passback 暂留 "" (未实测; r3-r10 实证 minimax 主路径
// 不走 thinking 模式, 等真消费者驱动再补). ProviderKind=direct.
ToolNameRegex: `^[a-zA-Z0-9_-]+$`,
ReasoningPassbackMode: "",
ProviderKind: "direct",
ContextWindow: 200_000,
MaxOutputTokens: 16_000,
InputPricePer1M: 0.27,
OutputPricePer1M: 0.95,
SupportsCaching: true,
SupportsThinking: true,
SupportsVision: false,
CachingMinTokens: 1024,
MaxTools: 0, // probe 实测 @256 未发现上限(2026-04)
},
{
ID: "MiniMax-M2",
DisplayName: "MiniMax M2",
Provider: "minimax",
// ADR-0007: MiniMax OpenAI-compat 路径 ^[a-zA-Z0-9_-]+$.
// reasoning passback 暂留 "" (未实测; r3-r10 实证 minimax 主路径
// 不走 thinking 模式, 等真消费者驱动再补). ProviderKind=direct.
ToolNameRegex: `^[a-zA-Z0-9_-]+$`,
ReasoningPassbackMode: "",
ProviderKind: "direct",
ContextWindow: 200_000,
MaxOutputTokens: 16_000,
InputPricePer1M: 0.255,
OutputPricePer1M: 1.00,
SupportsCaching: true,
SupportsThinking: false,
SupportsVision: false,
CachingMinTokens: 1024,
MaxTools: 0, // probe 实测 @256 未发现上限(2026-04)
},
{
ID: "MiniMax-M1",
DisplayName: "MiniMax M1 (1M context)",
Provider: "minimax",
// ADR-0007: MiniMax OpenAI-compat 路径 ^[a-zA-Z0-9_-]+$.
// reasoning passback 暂留 "" (未实测; r3-r10 实证 minimax 主路径
// 不走 thinking 模式, 等真消费者驱动再补). ProviderKind=direct.
ToolNameRegex: `^[a-zA-Z0-9_-]+$`,
ReasoningPassbackMode: "",
ProviderKind: "direct",
ContextWindow: 1_000_000,
MaxOutputTokens: 16_000,
InputPricePer1M: 0.40,
OutputPricePer1M: 1.76,
SupportsCaching: false,
SupportsThinking: false,
SupportsVision: false,
CachingMinTokens: 0,
MaxTools: 0, // probe 实测 @256 未发现上限(2026-04)
},
}