package providers import ( "context" "encoding/json" "fmt" "strings" "gophergate/internal/config" "gophergate/internal/models" "github.com/go-resty/resty/v2" ) type MoonshotProvider struct { client *resty.Client config config.MoonshotConfig apiKey string } func NewMoonshotProvider(cfg config.MoonshotConfig, apiKey string) *MoonshotProvider { return &MoonshotProvider{ client: resty.New(), config: cfg, apiKey: strings.TrimSpace(apiKey), } } func (p *MoonshotProvider) Name() string { return "moonshot" } func (p *MoonshotProvider) ChatCompletion(ctx context.Context, req *models.UnifiedRequest) (*models.ChatCompletionResponse, error) { messagesJSON, err := MessagesToOpenAIJSON(req.Messages) if err != nil { return nil, fmt.Errorf("failed to convert messages: %w", err) } body := BuildOpenAIBody(req, messagesJSON, false) if strings.Contains(strings.ToLower(req.Model), "kimi-k2.5") { if maxTokens, ok := body["max_tokens"]; ok { delete(body, "max_tokens") body["max_completion_tokens"] = maxTokens } } baseURL := strings.TrimRight(p.config.BaseURL, "/") resp, err := p.client.R(). SetContext(ctx). SetHeader("Authorization", "Bearer "+p.apiKey). SetHeader("Content-Type", "application/json"). SetHeader("Accept", "application/json"). SetBody(body). Post(fmt.Sprintf("%s/chat/completions", baseURL)) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } if !resp.IsSuccess() { return nil, fmt.Errorf("Moonshot API error (%d): %s", resp.StatusCode(), resp.String()) } var respJSON map[string]interface{} if err := json.Unmarshal(resp.Body(), &respJSON); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return ParseOpenAIResponse(respJSON, req.Model) } func (p *MoonshotProvider) ChatCompletionStream(ctx context.Context, req *models.UnifiedRequest) (<-chan *models.ChatCompletionStreamResponse, error) { messagesJSON, err := MessagesToOpenAIJSON(req.Messages) if err != nil { return nil, fmt.Errorf("failed to convert messages: %w", err) } body := BuildOpenAIBody(req, messagesJSON, true) if strings.Contains(strings.ToLower(req.Model), "kimi-k2.5") { if maxTokens, ok := body["max_tokens"]; ok { delete(body, "max_tokens") body["max_completion_tokens"] = maxTokens } } baseURL := strings.TrimRight(p.config.BaseURL, "/") resp, err := p.client.R(). SetContext(ctx). SetHeader("Authorization", "Bearer "+p.apiKey). SetHeader("Content-Type", "application/json"). SetHeader("Accept", "text/event-stream"). SetBody(body). SetDoNotParseResponse(true). Post(fmt.Sprintf("%s/chat/completions", baseURL)) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } if !resp.IsSuccess() { return nil, fmt.Errorf("Moonshot API error (%d): %s", resp.StatusCode(), resp.String()) } ch := make(chan *models.ChatCompletionStreamResponse) go func() { defer close(ch) if err := StreamOpenAI(resp.RawBody(), ch); err != nil { fmt.Printf("Moonshot Stream error: %v\n", err) } }() return ch, nil }