Problem
Currently, errors from the provider clients (clients/anthropic, clients/openai, clients/google) bubble up as the raw SDK error types wrapped with fmt.Errorf. This leaks SDK-specific shapes into downstream code: any package that wants to introspect errors (HTTP status, rate-limit hints, request IDs, model refusals, etc.) has to import the underlying SDK.
Concrete example surfaced by the robust package (#209): its DefaultErrorClassifier cannot call into *anthropic.Error.StatusCode, *openai.Error.StatusCode, or genai.APIError.Code without importing all three SDKs, which would force every adopter of robust to depend on all three. The current workaround is regex-based string inspection of the error message — functional but brittle and uninformative.
Proposed direction
Wrap every client error at the boundary into a gai-native error type that exposes the information we care about via small, stable interfaces. Rough sketch:
// In gai or a new gai/errors subpackage:
type StatusCoder interface { error; StatusCode() int }
type RequestIDer interface { error; RequestID() string }
// ... other capability-style interfaces as needed
// Each client wraps SDK errors into a concrete type that implements the relevant interfaces.
Consumers then use errors.As (or errors.AsType once Go 1.26 is the floor) against the interfaces, never against SDK concrete types. The SDK error remains reachable via errors.Unwrap as an escape hatch.
Benefits
robust.DefaultErrorClassifier becomes precise and SDK-agnostic — drop the regex.
- Downstream consumers (logging, metrics, custom retry policies, error reporting to users) get a uniform surface.
- Keeps the minimal-abstraction spirit: SDK errors are still unwrappable for escape-hatch access.
Open questions
- Which information is worth surfacing on v1? Candidates: HTTP status, request ID, rate-limit hint (if providers supply it), model refusal flag, provider name.
- One top-level error type with multiple interface implementations, vs. several distinct types.
- Whether to also normalize to typed errors that can be compared with
errors.Is (e.g. ErrRateLimited, ErrAuthentication).
References
Problem
Currently, errors from the provider clients (
clients/anthropic,clients/openai,clients/google) bubble up as the raw SDK error types wrapped withfmt.Errorf. This leaks SDK-specific shapes into downstream code: any package that wants to introspect errors (HTTP status, rate-limit hints, request IDs, model refusals, etc.) has to import the underlying SDK.Concrete example surfaced by the
robustpackage (#209): itsDefaultErrorClassifiercannot call into*anthropic.Error.StatusCode,*openai.Error.StatusCode, orgenai.APIError.Codewithout importing all three SDKs, which would force every adopter ofrobustto depend on all three. The current workaround is regex-based string inspection of the error message — functional but brittle and uninformative.Proposed direction
Wrap every client error at the boundary into a gai-native error type that exposes the information we care about via small, stable interfaces. Rough sketch:
Consumers then use
errors.As(orerrors.AsTypeonce Go 1.26 is the floor) against the interfaces, never against SDK concrete types. The SDK error remains reachable viaerrors.Unwrapas an escape hatch.Benefits
robust.DefaultErrorClassifierbecomes precise and SDK-agnostic — drop the regex.Open questions
errors.Is(e.g.ErrRateLimited,ErrAuthentication).References
robust/classify.gofor the regex workaround that this issue would let us delete.