1package anthropic
2
3import (
4 "cmp"
5 "errors"
6 "io"
7 "net/http"
8 "regexp"
9 "strconv"
10 "strings"
11
12 "charm.land/fantasy"
13 "github.com/charmbracelet/anthropic-sdk-go"
14)
15
16var anthropicContextPattern = regexp.MustCompile(`prompt is too long:\s*(\d+)\s*tokens?\s*>\s*(\d+)\s*maximum`)
17
18func toProviderErr(err error) error {
19 var apiErr *anthropic.Error
20 if errors.As(err, &apiErr) {
21 providerErr := &fantasy.ProviderError{
22 Title: cmp.Or(fantasy.ErrorTitleForStatusCode(apiErr.StatusCode), "provider request failed"),
23 Message: apiErr.Error(),
24 Cause: apiErr,
25 URL: apiErr.Request.URL.String(),
26 StatusCode: apiErr.StatusCode,
27 RequestBody: apiErr.DumpRequest(true),
28 ResponseHeaders: toHeaderMap(apiErr.Response.Header),
29 ResponseBody: apiErr.DumpResponse(true),
30 }
31
32 parseContextTooLargeError(apiErr.Error(), providerErr)
33
34 return providerErr
35 }
36 // Wrap in a `ProviderError` so `.IsRetriable()` works.
37 if errors.Is(err, io.ErrUnexpectedEOF) {
38 return &fantasy.ProviderError{
39 Title: "stream transport error",
40 Message: err.Error(),
41 Cause: err,
42 }
43 }
44 return err
45}
46
47func parseContextTooLargeError(message string, providerErr *fantasy.ProviderError) {
48 matches := anthropicContextPattern.FindStringSubmatch(message)
49 if matches == nil {
50 return
51 }
52
53 providerErr.ContextTooLargeErr = true
54 providerErr.ContextUsedTokens, _ = strconv.Atoi(matches[1])
55 providerErr.ContextMaxTokens, _ = strconv.Atoi(matches[2])
56}
57
58func toHeaderMap(in http.Header) (out map[string]string) {
59 out = make(map[string]string, len(in))
60 for k, v := range in {
61 if l := len(v); l > 0 {
62 out[k] = v[l-1]
63 in[strings.ToLower(k)] = v
64 }
65 }
66 return out
67}