feat(providers): add extra_body config to inject custom fields into request body

Allow configuring provider-specific fields like reasoning_split for minimax via
the model config's extra_body map. These fields are merged into the request
body last, giving them precedence over default values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
uiyzzi
2026-03-22 15:49:25 +08:00
parent dd82794255
commit a005e5bb70
7 changed files with 165 additions and 5 deletions
+5 -4
View File
@@ -674,10 +674,11 @@ type ModelConfig struct {
Workspace string `json:"workspace,omitempty"` // Workspace path for CLI-based providers
// Optional optimizations
RPM int `json:"rpm,omitempty"` // Requests per minute limit
MaxTokensField string `json:"max_tokens_field,omitempty"` // Field name for max tokens (e.g., "max_completion_tokens")
RequestTimeout int `json:"request_timeout,omitempty"`
ThinkingLevel string `json:"thinking_level,omitempty"` // Extended thinking: off|low|medium|high|xhigh|adaptive
RPM int `json:"rpm,omitempty"` // Requests per minute limit
MaxTokensField string `json:"max_tokens_field,omitempty"` // Field name for max tokens (e.g., "max_completion_tokens")
RequestTimeout int `json:"request_timeout,omitempty"`
ThinkingLevel string `json:"thinking_level,omitempty"` // Extended thinking: off|low|medium|high|xhigh|adaptive
ExtraBody map[string]any `json:"extra_body,omitempty"` // Additional fields to inject into request body
}
// Validate checks if the ModelConfig has all required fields.
+56
View File
@@ -1099,3 +1099,59 @@ func TestConfigLogLevelEmpty(t *testing.T) {
t.Errorf("LogLevel = %q, want \"fatal\"", cfg.Agents.Defaults.LogLevel)
}
}
func TestDefaultConfig_MinimaxExtraBody(t *testing.T) {
cfg := DefaultConfig()
var minimaxCfg *ModelConfig
for i := range cfg.ModelList {
if cfg.ModelList[i].Model == "minimax/MiniMax-M2.5" {
minimaxCfg = &cfg.ModelList[i]
break
}
}
if minimaxCfg == nil {
t.Fatal("Minimax model not found in ModelList")
}
if minimaxCfg.ExtraBody == nil {
t.Fatal("Minimax ExtraBody should not be nil")
}
if got, ok := minimaxCfg.ExtraBody["reasoning_split"]; !ok || got != true {
t.Fatalf("Minimax ExtraBody[reasoning_split] = %v, want true", got)
}
}
func TestModelConfig_ExtraBodyRoundTrip(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "config.json")
cfg := &Config{
ModelList: []ModelConfig{
{
ModelName: "test-model",
Model: "openai/test",
APIKey: "sk-test",
ExtraBody: map[string]any{"custom_field": "value", "num_field": 42},
},
},
}
if err := SaveConfig(cfgPath, cfg); err != nil {
t.Fatalf("SaveConfig error: %v", err)
}
loaded, err := LoadConfig(cfgPath)
if err != nil {
t.Fatalf("LoadConfig error: %v", err)
}
if loaded.ModelList[0].ExtraBody == nil {
t.Fatal("ExtraBody should not be nil after round-trip")
}
if got := loaded.ModelList[0].ExtraBody["custom_field"]; got != "value" {
t.Errorf("ExtraBody[custom_field] = %v, want value", got)
}
if got := loaded.ModelList[0].ExtraBody["num_field"]; got != float64(42) {
t.Errorf("ExtraBody[num_field] = %v, want 42", got)
}
}
+1
View File
@@ -376,6 +376,7 @@ func DefaultConfig() *Config {
Model: "minimax/MiniMax-M2.5",
APIBase: "https://api.minimaxi.com/v1",
APIKey: "",
ExtraBody: map[string]any{"reasoning_split": true},
},
// LongCat - https://longcat.chat/platform