errors.go
88 lines1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package assistant
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)
var (
// ErrNoAPIKey is returned when no API key is provided.
ErrNoAPIKey = errors.New("API key is required")
// ErrRateLimited is returned when rate limited by the API.
ErrRateLimited = errors.New("rate limited")
// ErrEmptyResponse is returned when the API returns an empty response.
ErrEmptyResponse = errors.New("empty response")
)
// APIError represents an error from the AI provider's API.
type APIError struct {
StatusCode int // HTTP status code
Type string // Error type from the API
Message string // Error message
}
// Error returns a formatted error string including the status code and message.
func (e *APIError) Error() string {
if e.Type != "" {
return fmt.Sprintf("API error %d (%s): %s", e.StatusCode, e.Type, e.Message)
}
return fmt.Sprintf("API error %d: %s", e.StatusCode, e.Message)
}
// IsRateLimited returns true if the error is a rate limit error.
func IsRateLimited(err error) bool {
if errors.Is(err, ErrRateLimited) {
return true
}
var apiErr *APIError
if errors.As(err, &apiErr) {
return apiErr.StatusCode == 429
}
return false
}
// IsAuthError returns true if the error is an authentication error.
func IsAuthError(err error) bool {
if errors.Is(err, ErrNoAPIKey) {
return true
}
var apiErr *APIError
if errors.As(err, &apiErr) {
return apiErr.StatusCode == 401 || apiErr.StatusCode == 403
}
return false
}
// ParseErrorResponse reads an HTTP error response body and constructs an APIError.
// Both Anthropic and OpenAI use the same {"error": {"type": "...", "message": "..."}} shape.
// If the body cannot be parsed, the raw body text is used as the error message.
//
// This is provider-facing: used by provider subpackages to handle API errors.
func ParseErrorResponse(resp *http.Response) error {
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) // 1MB max
var errResp struct {
Error struct {
Type string `json:"type"`
Message string `json:"message"`
} `json:"error"`
}
if json.Unmarshal(body, &errResp) == nil && errResp.Error.Message != "" {
return &APIError{
StatusCode: resp.StatusCode,
Type: errResp.Error.Type,
Message: errResp.Error.Message,
}
}
return &APIError{
StatusCode: resp.StatusCode,
Message: string(body),
}
}