Files
mistral-go-sdk/request.go
vikingowl 75a726cbe2 feat: Phase 4 models + files — CRUD, multipart upload, signed URLs
Add model and file management:
- model/model.go: ModelCard (unified base/fine-tuned), ModelCapabilities
- file/file.go: File, ListParams, Purpose/SampleType/Source enums
- ListModels, GetModel, DeleteModel service methods
- UploadFile (multipart/form-data), ListFiles (query params), GetFile,
  DeleteFile, GetFileContent (binary stream), GetFileURL (signed URL)
- doMultipart() HTTP helper for file uploads
- 13 new tests covering all endpoints including multipart parsing
2026-03-05 19:41:39 +01:00

154 lines
3.9 KiB
Go

package mistral
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
)
func (c *Client) do(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, body)
if err != nil {
return nil, fmt.Errorf("mistral: create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
req.Header.Set("Accept", "application/json")
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("mistral: send request: %w", err)
}
return resp, nil
}
func (c *Client) doJSON(ctx context.Context, method, path string, reqBody, respBody any) error {
var body io.Reader
if reqBody != nil {
data, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("mistral: marshal request: %w", err)
}
body = bytes.NewReader(data)
}
resp, err := c.do(ctx, method, path, body)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return parseAPIError(resp)
}
if respBody != nil {
if err := json.NewDecoder(resp.Body).Decode(respBody); err != nil {
return fmt.Errorf("mistral: decode response: %w", err)
}
}
return nil
}
func (c *Client) doStream(ctx context.Context, method, path string, reqBody any) (*http.Response, error) {
var body io.Reader
if reqBody != nil {
data, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("mistral: marshal request: %w", err)
}
body = bytes.NewReader(data)
}
resp, err := c.do(ctx, method, path, body)
if err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
defer resp.Body.Close()
return nil, parseAPIError(resp)
}
return resp, nil
}
func (c *Client) doMultipart(ctx context.Context, path string, filename string, file io.Reader, fields map[string]string, respBody any) error {
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
part, err := w.CreateFormFile("file", filename)
if err != nil {
return fmt.Errorf("mistral: create form file: %w", err)
}
if _, err := io.Copy(part, file); err != nil {
return fmt.Errorf("mistral: copy file data: %w", err)
}
for k, v := range fields {
if err := w.WriteField(k, v); err != nil {
return fmt.Errorf("mistral: write field %s: %w", k, err)
}
}
if err := w.Close(); err != nil {
return fmt.Errorf("mistral: close multipart: %w", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+path, &buf)
if err != nil {
return fmt.Errorf("mistral: create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", w.FormDataContentType())
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("mistral: send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return parseAPIError(resp)
}
if respBody != nil {
if err := json.NewDecoder(resp.Body).Decode(respBody); err != nil {
return fmt.Errorf("mistral: decode response: %w", err)
}
}
return nil
}
func parseAPIError(resp *http.Response) error {
body, err := io.ReadAll(resp.Body)
if err != nil {
return &APIError{
StatusCode: resp.StatusCode,
Message: fmt.Sprintf("failed to read error response: %v", err),
}
}
var envelope struct {
Detail string `json:"detail"`
Message string `json:"message"`
Type string `json:"type"`
Param string `json:"param"`
Code string `json:"code"`
}
if err := json.Unmarshal(body, &envelope); err == nil {
msg := envelope.Message
if msg == "" {
msg = envelope.Detail
}
if msg == "" {
msg = string(body)
}
return &APIError{
StatusCode: resp.StatusCode,
Type: envelope.Type,
Message: msg,
Param: envelope.Param,
Code: envelope.Code,
}
}
return &APIError{
StatusCode: resp.StatusCode,
Message: string(body),
}
}