detached 4a8d866

Correct MiniMax-M3 request handling and pricing

commit
4a8d86631df8745fede087975213fa9120911fa8
author
patriceckhart · · 2026-07-29 16:43 UTC
parent
9ec8550
signature
unsigned
4 files · +103 -21
M packages/provider/anthropic.go +13 -10
@@ -195,7 +195,7 @@ type anthRequest struct {
195195 // the same family when reached through an Anthropic-Messages-
196196 // compatible proxy whose catalog row predates the flag.
197197 func usesAdaptiveThinking(m Model) bool {
198- if m.AdaptiveThinking {
198+ if m.AdaptiveThinking || m.AdaptiveThinkingCompat {
199199 return true
200200 }
201201 id := strings.ToLower(m.ID)
@@ -231,16 +231,17 @@ func (c *anthropicClient) buildRequest(req Request) (*anthRequest, error) {
231231 }
232232
233233 adaptive := usesAdaptiveThinking(m)
234+ adaptiveEffort := adaptive && !m.AdaptiveThinkingCompat
234235
235236 out := &anthRequest{
236237 Model: req.Model,
237238 MaxTokens: maxTok,
238239 Stream: true,
239240 }
240- // Adaptive-thinking models reject non-default sampling params
241- // (temperature/top_p/top_k -> 400). Only forward temperature for
242- // models that accept it.
243- if !adaptive {
241+ // Anthropic's adaptive-thinking models reject non-default sampling
242+ // params. Compatible implementations may use the adaptive thinking type
243+ // while continuing to support temperature.
244+ if !adaptiveEffort {
244245 out.Temperature = req.Temperature
245246 }
246247
@@ -285,12 +286,14 @@ func (c *anthropicClient) buildRequest(req Request) (*anthRequest, error) {
285286
286287 if req.Reasoning != "" && m.Reasoning {
287288 if adaptive {
288- // Adaptive-thinking models (Opus 4.7+): the model decides when
289- // and how much to think; depth is steered by output_config.effort.
290- // Explicit budgets are rejected with a 400, so none is sent.
289+ // Adaptive-thinking models decide when and how much to think and
290+ // reject explicit budgets. Anthropic models also accept the
291+ // output_config.effort extension; compatible implementations may not.
291292 out.Thinking = &anthThinking{Type: "adaptive"}
292- if effort := AnthropicAdaptiveEffort(req.Reasoning); effort != "" {
293- out.OutputConfig = &anthOutputConfig{Effort: effort}
293+ if adaptiveEffort {
294+ if effort := AnthropicAdaptiveEffort(req.Reasoning); effort != "" {
295+ out.OutputConfig = &anthOutputConfig{Effort: effort}
296+ }
294297 }
295298 } else {
296299 budget := anthropicReasoningBudget(req.Reasoning)
@@ -195,7 +195,7 @@ type anthRequest struct {
195 // the same family when reached through an Anthropic-Messages-195 // the same family when reached through an Anthropic-Messages-
196 // compatible proxy whose catalog row predates the flag.196 // compatible proxy whose catalog row predates the flag.
197 func usesAdaptiveThinking(m Model) bool {197 func usesAdaptiveThinking(m Model) bool {
198- if m.AdaptiveThinking {198+ if m.AdaptiveThinking || m.AdaptiveThinkingCompat {
199 return true199 return true
200 }200 }
201 id := strings.ToLower(m.ID)201 id := strings.ToLower(m.ID)
@@ -231,16 +231,17 @@ func (c *anthropicClient) buildRequest(req Request) (*anthRequest, error) {
231 }231 }
232 232
233 adaptive := usesAdaptiveThinking(m)233 adaptive := usesAdaptiveThinking(m)
234+ adaptiveEffort := adaptive && !m.AdaptiveThinkingCompat
234 235
235 out := &anthRequest{236 out := &anthRequest{
236 Model: req.Model,237 Model: req.Model,
237 MaxTokens: maxTok,238 MaxTokens: maxTok,
238 Stream: true,239 Stream: true,
239 }240 }
240- // Adaptive-thinking models reject non-default sampling params241+ // Anthropic's adaptive-thinking models reject non-default sampling
241- // (temperature/top_p/top_k -> 400). Only forward temperature for242+ // params. Compatible implementations may use the adaptive thinking type
242- // models that accept it.243+ // while continuing to support temperature.
243- if !adaptive {244+ if !adaptiveEffort {
244 out.Temperature = req.Temperature245 out.Temperature = req.Temperature
245 }246 }
246 247
@@ -285,12 +286,14 @@ func (c *anthropicClient) buildRequest(req Request) (*anthRequest, error) {
285 286
286 if req.Reasoning != "" && m.Reasoning {287 if req.Reasoning != "" && m.Reasoning {
287 if adaptive {288 if adaptive {
288- // Adaptive-thinking models (Opus 4.7+): the model decides when289+ // Adaptive-thinking models decide when and how much to think and
289- // and how much to think; depth is steered by output_config.effort.290+ // reject explicit budgets. Anthropic models also accept the
290- // Explicit budgets are rejected with a 400, so none is sent.291+ // output_config.effort extension; compatible implementations may not.
291 out.Thinking = &anthThinking{Type: "adaptive"}292 out.Thinking = &anthThinking{Type: "adaptive"}
292- if effort := AnthropicAdaptiveEffort(req.Reasoning); effort != "" {293+ if adaptiveEffort {
293- out.OutputConfig = &anthOutputConfig{Effort: effort}294+ if effort := AnthropicAdaptiveEffort(req.Reasoning); effort != "" {
295+ out.OutputConfig = &anthOutputConfig{Effort: effort}
296+ }
294 }297 }
295 } else {298 } else {
296 budget := anthropicReasoningBudget(req.Reasoning)299 budget := anthropicReasoningBudget(req.Reasoning)
M packages/provider/catalog_builtin.go +2 -2
@@ -229,11 +229,11 @@ var builtinCatalog = []Model{
229229 // ----- minimax -----
230230 {Provider: "minimax", ID: "MiniMax-M2.7", DisplayName: "MiniMax-M2.7", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimax.io/anthropic"},
231231 {Provider: "minimax", ID: "MiniMax-M2.7-highspeed", DisplayName: "MiniMax-M2.7-highspeed", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimax.io/anthropic"},
232- {Provider: "minimax", ID: "MiniMax-M3", DisplayName: "MiniMax-M3", ContextWindow: 1000000, MaxOutput: 131072, Reasoning: true, AdaptiveThinking: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.12, BaseURL: "https://api.minimax.io/anthropic"},
232+ {Provider: "minimax", ID: "MiniMax-M3", DisplayName: "MiniMax-M3", ContextWindow: 1000000, MaxOutput: 131072, Reasoning: true, AdaptiveThinkingCompat: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceTierInputTokens: 512000, PriceInputAbove: 0.6, PriceOutputAbove: 2.4, PriceCacheReadAbove: 0.12, BaseURL: "https://api.minimax.io/anthropic"},
233233 // ----- minimax-cn -----
234234 {Provider: "minimax-cn", ID: "MiniMax-M2.7", DisplayName: "MiniMax-M2.7", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimaxi.com/anthropic"},
235235 {Provider: "minimax-cn", ID: "MiniMax-M2.7-highspeed", DisplayName: "MiniMax-M2.7-highspeed", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimaxi.com/anthropic"},
236- {Provider: "minimax-cn", ID: "MiniMax-M3", DisplayName: "MiniMax-M3", ContextWindow: 1000000, MaxOutput: 131072, Reasoning: true, AdaptiveThinking: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.12, BaseURL: "https://api.minimaxi.com/anthropic"},
236+ {Provider: "minimax-cn", ID: "MiniMax-M3", DisplayName: "MiniMax-M3", ContextWindow: 1000000, MaxOutput: 131072, Reasoning: true, AdaptiveThinkingCompat: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceTierInputTokens: 512000, PriceInputAbove: 0.6, PriceOutputAbove: 2.4, PriceCacheReadAbove: 0.12, BaseURL: "https://api.minimaxi.com/anthropic"},
237237 // ----- mistral -----
238238 {Provider: "mistral", ID: "codestral-latest", DisplayName: "Codestral (latest)", ContextWindow: 256000, MaxOutput: 4096, Reasoning: false, PriceInput: 0.3, PriceOutput: 0.9, PriceCacheRead: 0, BaseURL: "https://api.mistral.ai"},
239239 {Provider: "mistral", ID: "devstral-2512", DisplayName: "Devstral 2", ContextWindow: 262144, MaxOutput: 262144, Reasoning: false, PriceInput: 0.4, PriceOutput: 2, PriceCacheRead: 0, BaseURL: "https://api.mistral.ai"},
@@ -229,11 +229,11 @@ var builtinCatalog = []Model{
229 // ----- minimax -----229 // ----- minimax -----
230 {Provider: "minimax", ID: "MiniMax-M2.7", DisplayName: "MiniMax-M2.7", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimax.io/anthropic"},230 {Provider: "minimax", ID: "MiniMax-M2.7", DisplayName: "MiniMax-M2.7", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimax.io/anthropic"},
231 {Provider: "minimax", ID: "MiniMax-M2.7-highspeed", DisplayName: "MiniMax-M2.7-highspeed", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimax.io/anthropic"},231 {Provider: "minimax", ID: "MiniMax-M2.7-highspeed", DisplayName: "MiniMax-M2.7-highspeed", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimax.io/anthropic"},
232- {Provider: "minimax", ID: "MiniMax-M3", DisplayName: "MiniMax-M3", ContextWindow: 1000000, MaxOutput: 131072, Reasoning: true, AdaptiveThinking: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.12, BaseURL: "https://api.minimax.io/anthropic"},232+ {Provider: "minimax", ID: "MiniMax-M3", DisplayName: "MiniMax-M3", ContextWindow: 1000000, MaxOutput: 131072, Reasoning: true, AdaptiveThinkingCompat: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceTierInputTokens: 512000, PriceInputAbove: 0.6, PriceOutputAbove: 2.4, PriceCacheReadAbove: 0.12, BaseURL: "https://api.minimax.io/anthropic"},
233 // ----- minimax-cn -----233 // ----- minimax-cn -----
234 {Provider: "minimax-cn", ID: "MiniMax-M2.7", DisplayName: "MiniMax-M2.7", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimaxi.com/anthropic"},234 {Provider: "minimax-cn", ID: "MiniMax-M2.7", DisplayName: "MiniMax-M2.7", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimaxi.com/anthropic"},
235 {Provider: "minimax-cn", ID: "MiniMax-M2.7-highspeed", DisplayName: "MiniMax-M2.7-highspeed", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimaxi.com/anthropic"},235 {Provider: "minimax-cn", ID: "MiniMax-M2.7-highspeed", DisplayName: "MiniMax-M2.7-highspeed", ContextWindow: 204800, MaxOutput: 131072, Reasoning: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.06, PriceCacheWrite: 0.375, BaseURL: "https://api.minimaxi.com/anthropic"},
236- {Provider: "minimax-cn", ID: "MiniMax-M3", DisplayName: "MiniMax-M3", ContextWindow: 1000000, MaxOutput: 131072, Reasoning: true, AdaptiveThinking: true, PriceInput: 0.6, PriceOutput: 2.4, PriceCacheRead: 0.12, BaseURL: "https://api.minimaxi.com/anthropic"},236+ {Provider: "minimax-cn", ID: "MiniMax-M3", DisplayName: "MiniMax-M3", ContextWindow: 1000000, MaxOutput: 131072, Reasoning: true, AdaptiveThinkingCompat: true, PriceInput: 0.3, PriceOutput: 1.2, PriceCacheRead: 0.06, PriceTierInputTokens: 512000, PriceInputAbove: 0.6, PriceOutputAbove: 2.4, PriceCacheReadAbove: 0.12, BaseURL: "https://api.minimaxi.com/anthropic"},
237 // ----- mistral -----237 // ----- mistral -----
238 {Provider: "mistral", ID: "codestral-latest", DisplayName: "Codestral (latest)", ContextWindow: 256000, MaxOutput: 4096, Reasoning: false, PriceInput: 0.3, PriceOutput: 0.9, PriceCacheRead: 0, BaseURL: "https://api.mistral.ai"},238 {Provider: "mistral", ID: "codestral-latest", DisplayName: "Codestral (latest)", ContextWindow: 256000, MaxOutput: 4096, Reasoning: false, PriceInput: 0.3, PriceOutput: 0.9, PriceCacheRead: 0, BaseURL: "https://api.mistral.ai"},
239 {Provider: "mistral", ID: "devstral-2512", DisplayName: "Devstral 2", ContextWindow: 262144, MaxOutput: 262144, Reasoning: false, PriceInput: 0.4, PriceOutput: 2, PriceCacheRead: 0, BaseURL: "https://api.mistral.ai"},239 {Provider: "mistral", ID: "devstral-2512", DisplayName: "Devstral 2", ContextWindow: 262144, MaxOutput: 262144, Reasoning: false, PriceInput: 0.4, PriceOutput: 2, PriceCacheRead: 0, BaseURL: "https://api.mistral.ai"},
M packages/provider/models.go +32 -9
@@ -23,11 +23,22 @@ type Model struct {
2323 // output_config.effort and omits temperature for these models.
2424 AdaptiveThinking bool
2525
26- // Prices are USD per 1M tokens.
27- PriceInput float64
28- PriceOutput float64
29- PriceCacheRead float64
30- PriceCacheWrite float64
26+ // AdaptiveThinkingCompat marks Anthropic-compatible models that require
27+ // thinking:{type:"adaptive"} but still accept sampling parameters and do
28+ // not support Anthropic's output_config.effort extension.
29+ AdaptiveThinkingCompat bool
30+
31+ // Prices are USD per 1M tokens. Price*Above fields apply to all token
32+ // classes when the total prompt size exceeds PriceTierInputTokens.
33+ PriceInput float64
34+ PriceOutput float64
35+ PriceCacheRead float64
36+ PriceCacheWrite float64
37+ PriceTierInputTokens int
38+ PriceInputAbove float64
39+ PriceOutputAbove float64
40+ PriceCacheReadAbove float64
41+ PriceCacheWriteAbove float64
3142
3243 // Speculative marks models whose ids are known from the upstream
3344 // vendor's CLI but not yet live on their public API. They'll 404
@@ -491,9 +502,21 @@ func ModelsForProvider(provider string) []Model {
491502
492503 // ComputeCost returns the USD cost for the given usage on model m.
493504 func ComputeCost(m Model, u Usage) float64 {
505+ inputPrice := m.PriceInput
506+ outputPrice := m.PriceOutput
507+ cacheReadPrice := m.PriceCacheRead
508+ cacheWritePrice := m.PriceCacheWrite
509+ promptTokens := u.InputTokens + u.CacheReadTokens + u.CacheWriteTokens
510+ if m.PriceTierInputTokens > 0 && promptTokens > m.PriceTierInputTokens {
511+ inputPrice = m.PriceInputAbove
512+ outputPrice = m.PriceOutputAbove
513+ cacheReadPrice = m.PriceCacheReadAbove
514+ cacheWritePrice = m.PriceCacheWriteAbove
515+ }
516+
494517 const per = 1_000_000.0
495- return float64(u.InputTokens)*m.PriceInput/per +
496- float64(u.OutputTokens)*m.PriceOutput/per +
497- float64(u.CacheReadTokens)*m.PriceCacheRead/per +
498- float64(u.CacheWriteTokens)*m.PriceCacheWrite/per
518+ return float64(u.InputTokens)*inputPrice/per +
519+ float64(u.OutputTokens)*outputPrice/per +
520+ float64(u.CacheReadTokens)*cacheReadPrice/per +
521+ float64(u.CacheWriteTokens)*cacheWritePrice/per
499522 }
@@ -23,11 +23,22 @@ type Model struct {
23 // output_config.effort and omits temperature for these models.23 // output_config.effort and omits temperature for these models.
24 AdaptiveThinking bool24 AdaptiveThinking bool
25 25
26- // Prices are USD per 1M tokens.26+ // AdaptiveThinkingCompat marks Anthropic-compatible models that require
27- PriceInput float6427+ // thinking:{type:"adaptive"} but still accept sampling parameters and do
28- PriceOutput float6428+ // not support Anthropic's output_config.effort extension.
29- PriceCacheRead float6429+ AdaptiveThinkingCompat bool
30- PriceCacheWrite float6430+
31+ // Prices are USD per 1M tokens. Price*Above fields apply to all token
32+ // classes when the total prompt size exceeds PriceTierInputTokens.
33+ PriceInput float64
34+ PriceOutput float64
35+ PriceCacheRead float64
36+ PriceCacheWrite float64
37+ PriceTierInputTokens int
38+ PriceInputAbove float64
39+ PriceOutputAbove float64
40+ PriceCacheReadAbove float64
41+ PriceCacheWriteAbove float64
31 42
32 // Speculative marks models whose ids are known from the upstream43 // Speculative marks models whose ids are known from the upstream
33 // vendor's CLI but not yet live on their public API. They'll 40444 // vendor's CLI but not yet live on their public API. They'll 404
@@ -491,9 +502,21 @@ func ModelsForProvider(provider string) []Model {
491 502
492 // ComputeCost returns the USD cost for the given usage on model m.503 // ComputeCost returns the USD cost for the given usage on model m.
493 func ComputeCost(m Model, u Usage) float64 {504 func ComputeCost(m Model, u Usage) float64 {
505+ inputPrice := m.PriceInput
506+ outputPrice := m.PriceOutput
507+ cacheReadPrice := m.PriceCacheRead
508+ cacheWritePrice := m.PriceCacheWrite
509+ promptTokens := u.InputTokens + u.CacheReadTokens + u.CacheWriteTokens
510+ if m.PriceTierInputTokens > 0 && promptTokens > m.PriceTierInputTokens {
511+ inputPrice = m.PriceInputAbove
512+ outputPrice = m.PriceOutputAbove
513+ cacheReadPrice = m.PriceCacheReadAbove
514+ cacheWritePrice = m.PriceCacheWriteAbove
515+ }
516+
494 const per = 1_000_000.0517 const per = 1_000_000.0
495- return float64(u.InputTokens)*m.PriceInput/per +518+ return float64(u.InputTokens)*inputPrice/per +
496- float64(u.OutputTokens)*m.PriceOutput/per +519+ float64(u.OutputTokens)*outputPrice/per +
497- float64(u.CacheReadTokens)*m.PriceCacheRead/per +520+ float64(u.CacheReadTokens)*cacheReadPrice/per +
498- float64(u.CacheWriteTokens)*m.PriceCacheWrite/per521+ float64(u.CacheWriteTokens)*cacheWritePrice/per
499 }522 }
M packages/provider/provider_test.go +56 -0
@@ -2,6 +2,7 @@ package provider
22
33 import (
44 "context"
5+ "math"
56 "net/http"
67 "net/http/httptest"
78 "strings"
@@ -62,6 +63,25 @@ func TestComputeCost(t *testing.T) {
6263 }
6364 }
6465
66+func TestComputeCostInputTier(t *testing.T) {
67+ m := Model{
68+ PriceInput: 1, PriceOutput: 2, PriceCacheRead: 0.1,
69+ PriceTierInputTokens: 100,
70+ PriceInputAbove: 3, PriceOutputAbove: 4, PriceCacheReadAbove: 0.2,
71+ }
72+ below := ComputeCost(m, Usage{InputTokens: 50, OutputTokens: 10, CacheReadTokens: 50})
73+ wantBelow := (50.0*1 + 10*2 + 50*0.1) / 1_000_000
74+ if math.Abs(below-wantBelow) > 1e-12 {
75+ t.Fatalf("below-tier cost=%v want=%v", below, wantBelow)
76+ }
77+
78+ above := ComputeCost(m, Usage{InputTokens: 51, OutputTokens: 10, CacheReadTokens: 50})
79+ wantAbove := (51.0*3 + 10*4 + 50*0.2) / 1_000_000
80+ if math.Abs(above-wantAbove) > 1e-12 {
81+ t.Fatalf("above-tier cost=%v want=%v", above, wantAbove)
82+ }
83+}
84+
6585 func TestAnthropicErrorStatus(t *testing.T) {
6686 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
6787 w.WriteHeader(http.StatusUnauthorized)
@@ -90,6 +110,42 @@ func TestOpenAIErrorStatus(t *testing.T) {
90110 }
91111 }
92112
113+func TestMiniMaxM3CatalogAndRequest(t *testing.T) {
114+ for _, provider := range []string{"minimax", "minimax-cn"} {
115+ m, err := FindModel(provider, "MiniMax-M3")
116+ if err != nil {
117+ t.Fatal(err)
118+ }
119+ if m.ContextWindow != 1000000 || m.MaxOutput != 131072 || !m.Reasoning || !m.AdaptiveThinkingCompat {
120+ t.Errorf("%s metadata = %+v", provider, m)
121+ }
122+ if m.PriceInput != 0.3 || m.PriceOutput != 1.2 || m.PriceCacheRead != 0.06 || m.PriceTierInputTokens != 512000 || m.PriceInputAbove != 0.6 || m.PriceOutputAbove != 2.4 || m.PriceCacheReadAbove != 0.12 {
123+ t.Errorf("%s pricing = %+v", provider, m)
124+ }
125+ }
126+
127+ c := NewAnthropicCompat("minimax", "x", "https://api.minimax.io/anthropic").(*anthropicClient)
128+ temp := float32(0.7)
129+ wire, err := c.buildRequest(Request{
130+ Model: "MiniMax-M3",
131+ Reasoning: "high",
132+ Temperature: &temp,
133+ Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}},
134+ })
135+ if err != nil {
136+ t.Fatal(err)
137+ }
138+ if wire.Thinking == nil || wire.Thinking.Type != "adaptive" || wire.Thinking.BudgetTokens != 0 {
139+ t.Fatalf("want adaptive thinking without a budget, got %+v", wire.Thinking)
140+ }
141+ if wire.OutputConfig != nil {
142+ t.Fatalf("MiniMax-M3 must not send output_config, got %+v", wire.OutputConfig)
143+ }
144+ if wire.Temperature == nil || *wire.Temperature != temp {
145+ t.Fatalf("MiniMax-M3 should keep temperature, got %v", wire.Temperature)
146+ }
147+}
148+
93149 func TestAnthropicAdaptiveThinking(t *testing.T) {
94150 c := NewAnthropic("x", "").(*anthropicClient)
95151 temp := float32(0.7)
@@ -2,6 +2,7 @@ package provider
2 2
3 import (3 import (
4 "context"4 "context"
5+ "math"
5 "net/http"6 "net/http"
6 "net/http/httptest"7 "net/http/httptest"
7 "strings"8 "strings"
@@ -62,6 +63,25 @@ func TestComputeCost(t *testing.T) {
62 }63 }
63 }64 }
64 65
66+func TestComputeCostInputTier(t *testing.T) {
67+ m := Model{
68+ PriceInput: 1, PriceOutput: 2, PriceCacheRead: 0.1,
69+ PriceTierInputTokens: 100,
70+ PriceInputAbove: 3, PriceOutputAbove: 4, PriceCacheReadAbove: 0.2,
71+ }
72+ below := ComputeCost(m, Usage{InputTokens: 50, OutputTokens: 10, CacheReadTokens: 50})
73+ wantBelow := (50.0*1 + 10*2 + 50*0.1) / 1_000_000
74+ if math.Abs(below-wantBelow) > 1e-12 {
75+ t.Fatalf("below-tier cost=%v want=%v", below, wantBelow)
76+ }
77+
78+ above := ComputeCost(m, Usage{InputTokens: 51, OutputTokens: 10, CacheReadTokens: 50})
79+ wantAbove := (51.0*3 + 10*4 + 50*0.2) / 1_000_000
80+ if math.Abs(above-wantAbove) > 1e-12 {
81+ t.Fatalf("above-tier cost=%v want=%v", above, wantAbove)
82+ }
83+}
84+
65 func TestAnthropicErrorStatus(t *testing.T) {85 func TestAnthropicErrorStatus(t *testing.T) {
66 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {86 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
67 w.WriteHeader(http.StatusUnauthorized)87 w.WriteHeader(http.StatusUnauthorized)
@@ -90,6 +110,42 @@ func TestOpenAIErrorStatus(t *testing.T) {
90 }110 }
91 }111 }
92 112
113+func TestMiniMaxM3CatalogAndRequest(t *testing.T) {
114+ for _, provider := range []string{"minimax", "minimax-cn"} {
115+ m, err := FindModel(provider, "MiniMax-M3")
116+ if err != nil {
117+ t.Fatal(err)
118+ }
119+ if m.ContextWindow != 1000000 || m.MaxOutput != 131072 || !m.Reasoning || !m.AdaptiveThinkingCompat {
120+ t.Errorf("%s metadata = %+v", provider, m)
121+ }
122+ if m.PriceInput != 0.3 || m.PriceOutput != 1.2 || m.PriceCacheRead != 0.06 || m.PriceTierInputTokens != 512000 || m.PriceInputAbove != 0.6 || m.PriceOutputAbove != 2.4 || m.PriceCacheReadAbove != 0.12 {
123+ t.Errorf("%s pricing = %+v", provider, m)
124+ }
125+ }
126+
127+ c := NewAnthropicCompat("minimax", "x", "https://api.minimax.io/anthropic").(*anthropicClient)
128+ temp := float32(0.7)
129+ wire, err := c.buildRequest(Request{
130+ Model: "MiniMax-M3",
131+ Reasoning: "high",
132+ Temperature: &temp,
133+ Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}},
134+ })
135+ if err != nil {
136+ t.Fatal(err)
137+ }
138+ if wire.Thinking == nil || wire.Thinking.Type != "adaptive" || wire.Thinking.BudgetTokens != 0 {
139+ t.Fatalf("want adaptive thinking without a budget, got %+v", wire.Thinking)
140+ }
141+ if wire.OutputConfig != nil {
142+ t.Fatalf("MiniMax-M3 must not send output_config, got %+v", wire.OutputConfig)
143+ }
144+ if wire.Temperature == nil || *wire.Temperature != temp {
145+ t.Fatalf("MiniMax-M3 should keep temperature, got %v", wire.Temperature)
146+ }
147+}
148+
93 func TestAnthropicAdaptiveThinking(t *testing.T) {149 func TestAnthropicAdaptiveThinking(t *testing.T) {
94 c := NewAnthropic("x", "").(*anthropicClient)150 c := NewAnthropic("x", "").(*anthropicClient)
95 temp := float32(0.7)151 temp := float32(0.7)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close