diff --git a/pkg/config/config.go b/pkg/config/config.go index 6af9438a5..aa33dd125 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -161,6 +161,7 @@ type ProvidersConfig struct { Nvidia ProviderConfig `json:"nvidia"` Moonshot ProviderConfig `json:"moonshot"` ShengSuanYun ProviderConfig `json:"shengsuanyun"` + MiniMax ProviderConfig `json:"minimax"` } type ProviderConfig struct { @@ -374,6 +375,9 @@ func (c *Config) GetAPIKey() string { if c.Providers.ShengSuanYun.APIKey != "" { return c.Providers.ShengSuanYun.APIKey } + if c.Providers.MiniMax.APIKey != "" { + return c.Providers.MiniMax.APIKey + } return "" } diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index fc78a182f..f876eb06f 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -21,9 +21,10 @@ import ( ) type HTTPProvider struct { - apiKey string - apiBase string - httpClient *http.Client + apiKey string + apiBase string + httpClient *http.Client + RequestSuffix string // Optional: override default "/chat/completions" } func NewHTTPProvider(apiKey, apiBase, proxy string) *HTTPProvider { @@ -94,7 +95,12 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too return nil, fmt.Errorf("failed to marshal request: %w", err) } - req, err := http.NewRequestWithContext(ctx, "POST", p.apiBase+"/chat/completions", bytes.NewReader(jsonData)) + suffix := "/chat/completions" + if p.RequestSuffix != "" { + suffix = p.RequestSuffix + } + + req, err := http.NewRequestWithContext(ctx, "POST", p.apiBase+suffix, bytes.NewReader(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } @@ -118,7 +124,6 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API request failed:\n Status: %d\n Body: %s", resp.StatusCode, string(body)) } - return p.parseResponse(body) } @@ -139,12 +144,20 @@ func (p *HTTPProvider) parseResponse(body []byte) (*LLMResponse, error) { FinishReason string `json:"finish_reason"` } `json:"choices"` Usage *UsageInfo `json:"usage"` + BaseResp *struct { + StatusCode int `json:"status_code"` + StatusMsg string `json:"status_msg"` + } `json:"base_resp"` } if err := json.Unmarshal(body, &apiResponse); err != nil { return nil, fmt.Errorf("failed to unmarshal response: %w", err) } + if apiResponse.BaseResp != nil && apiResponse.BaseResp.StatusCode != 0 { + return nil, fmt.Errorf("provider error: [%d] %s", apiResponse.BaseResp.StatusCode, apiResponse.BaseResp.StatusMsg) + } + if len(apiResponse.Choices) == 0 { return &LLMResponse{ Content: "", @@ -297,6 +310,16 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { apiBase = "https://router.shengsuanyun.com/api/v1" } } + case "minimax": + if cfg.Providers.MiniMax.APIKey != "" { + apiKey = cfg.Providers.MiniMax.APIKey + apiBase = cfg.Providers.MiniMax.APIBase + if apiBase == "" { + apiBase = "https://api.minimax.io/v1" + } + // MiniMax requires special provider creation + return MiniMaxProvider(apiKey, apiBase), nil + } case "claude-cli", "claudecode", "claude-code": workspace := cfg.Agents.Defaults.Workspace if workspace == "" { @@ -380,6 +403,14 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { apiBase = "https://integrate.api.nvidia.com/v1" } + case (strings.Contains(lowerModel, "minimax") || strings.HasPrefix(model, "minimax/")) && cfg.Providers.MiniMax.APIKey != "": + apiKey = cfg.Providers.MiniMax.APIKey + apiBase = cfg.Providers.MiniMax.APIBase + if apiBase == "" { + apiBase = "https://api.minimax.io/v1" + } + return MiniMaxProvider(apiKey, apiBase), nil + case cfg.Providers.VLLM.APIBase != "": apiKey = cfg.Providers.VLLM.APIKey apiBase = cfg.Providers.VLLM.APIBase diff --git a/pkg/providers/minimax_provider.go b/pkg/providers/minimax_provider.go new file mode 100644 index 000000000..535dcb0ee --- /dev/null +++ b/pkg/providers/minimax_provider.go @@ -0,0 +1,7 @@ +package providers + +func MiniMaxProvider(apiKey, apiBase string) *HTTPProvider { + p := NewHTTPProvider(apiKey, apiBase, "") + p.RequestSuffix = "/text/chatcompletion_v2" + return p +} diff --git a/pkg/providers/minimax_provider_test.go b/pkg/providers/minimax_provider_test.go new file mode 100644 index 000000000..b91c8cb30 --- /dev/null +++ b/pkg/providers/minimax_provider_test.go @@ -0,0 +1,28 @@ +package providers + +import ( + "context" + "os" + "testing" +) + +func TestMiniMaxProvider_Chat(t *testing.T) { + apiKey := os.Getenv("MINIMAX_API_KEY") + if apiKey == "" { + t.Skip("Skipping MiniMax integration test: MINIMAX_API_KEY not set") + } + + apiBase := "https://api.minimax.io/v1" + provider := MiniMaxProvider(apiKey, apiBase) + + resp, err := provider.Chat(context.Background(), []Message{{Role: "user", Content: "Hi"}}, nil, "M2-her", nil) + if err != nil { + t.Fatalf("Chat failed: %v", err) + } + + if resp.Content == "" { + t.Errorf("Expected non-empty content") + } + + t.Logf("Response: %s", resp.Content) +}