Files
gnoma/internal/provider/errors.go
vikingowl 85c643fdca feat: add foundation types, streaming, and provider interface
internal/message/ — Content discriminated union, Message, Usage,
StopReason, Response. 22 tests.

internal/stream/ — Stream pull-based iterator interface, Event types,
Accumulator (assembles Response from events). 8 tests.

internal/provider/ — Provider interface, Request, ToolDefinition,
Registry with factory pattern, ProviderError with HTTP status
classification. errors.AsType[E] for Go 1.26. 13 tests.

43 tests total, all passing.
2026-04-03 10:57:54 +02:00

80 lines
2.0 KiB
Go

package provider
import (
"fmt"
"time"
)
// ErrorKind classifies provider errors for retry decisions.
type ErrorKind int
const (
ErrTransient ErrorKind = iota + 1 // 429, 500, 502, 503, 529 — retry with backoff
ErrAuth // 401, 403 — don't retry
ErrBadRequest // 400 — don't retry, fix request
ErrNotFound // 404 — model/endpoint not found
ErrOverloaded // capacity exhausted — backoff + retry
)
func (k ErrorKind) String() string {
switch k {
case ErrTransient:
return "transient"
case ErrAuth:
return "auth"
case ErrBadRequest:
return "bad_request"
case ErrNotFound:
return "not_found"
case ErrOverloaded:
return "overloaded"
default:
return fmt.Sprintf("unknown(%d)", k)
}
}
// ProviderError wraps an SDK error with classification metadata.
type ProviderError struct {
Kind ErrorKind
Provider string
StatusCode int
Message string
Retryable bool
RetryAfter time.Duration // from Retry-After or rate limit headers
Err error // underlying SDK error
}
func (e *ProviderError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s %s (%d): %s: %v", e.Provider, e.Kind, e.StatusCode, e.Message, e.Err)
}
return fmt.Sprintf("%s %s (%d): %s", e.Provider, e.Kind, e.StatusCode, e.Message)
}
func (e *ProviderError) Unwrap() error {
return e.Err
}
// ClassifyHTTPStatus returns the ErrorKind and retryability for an HTTP status code.
func ClassifyHTTPStatus(status int) (ErrorKind, bool) {
switch {
case status == 401 || status == 403:
return ErrAuth, false
case status == 400:
return ErrBadRequest, false
case status == 404:
return ErrNotFound, false
case status == 429 || status == 529:
return ErrTransient, true
case status == 500 || status == 502 || status == 503:
return ErrTransient, true
case status == 504:
return ErrOverloaded, true
default:
if status >= 500 {
return ErrTransient, true
}
return ErrBadRequest, false
}
}