Classify normalizes err into a stable, user-facing payload used for retry handling, streamed terminal errors, and persisted last_error values.
(err error)
| 126 | // retry handling, streamed terminal errors, and persisted last_error |
| 127 | // values. |
| 128 | func Classify(err error) ClassifiedError { |
| 129 | if err == nil { |
| 130 | return ClassifiedError{} |
| 131 | } |
| 132 | |
| 133 | var wrapped *classifiedError |
| 134 | if errors.As(err, &wrapped) { |
| 135 | return normalizeClassification(wrapped.classified) |
| 136 | } |
| 137 | |
| 138 | structured := extractProviderErrorDetails(err) |
| 139 | message := strings.TrimSpace(err.Error()) |
| 140 | if message == "" && structured.detail == "" && structured.statusCode == 0 && structured.retryAfter <= 0 { |
| 141 | return ClassifiedError{} |
| 142 | } |
| 143 | |
| 144 | lower := strings.ToLower(message) |
| 145 | statusCode := structured.statusCode |
| 146 | if statusCode == 0 { |
| 147 | statusCode = extractStatusCode(lower) |
| 148 | } |
| 149 | provider := detectProvider(lower) |
| 150 | canceled := errors.Is(err, context.Canceled) || strings.Contains(lower, "context canceled") |
| 151 | interrupted := containsAny(lower, interruptedPatterns...) |
| 152 | if canceled || interrupted { |
| 153 | return normalizeClassification(ClassifiedError{ |
| 154 | Message: "The request was canceled before it completed.", |
| 155 | Detail: structured.detail, |
| 156 | Kind: codersdk.ChatErrorKindGeneric, |
| 157 | Provider: provider, |
| 158 | StatusCode: statusCode, |
| 159 | RetryAfter: structured.retryAfter, |
| 160 | }) |
| 161 | } |
| 162 | |
| 163 | if detail, ok := responsesAPIDiagnostic(lower, structured.detail); ok { |
| 164 | return normalizeClassification(ClassifiedError{ |
| 165 | Message: responsesAPIDiagnosticMessage, |
| 166 | Detail: detail, |
| 167 | Kind: codersdk.ChatErrorKindGeneric, |
| 168 | Provider: provider, |
| 169 | StatusCode: statusCode, |
| 170 | RetryAfter: structured.retryAfter, |
| 171 | }) |
| 172 | } |
| 173 | |
| 174 | if classified, ok := streamIncompleteClassification( |
| 175 | lower, |
| 176 | provider, |
| 177 | statusCode, |
| 178 | structured, |
| 179 | ); ok { |
| 180 | return classified |
| 181 | } |
| 182 | |
| 183 | // Chain-broken detection runs before the generic rule table so a |
| 184 | // 404 carrying a chain anchor failure is not classified as a |
| 185 | // generic non-retryable error. The chatloop retry callback uses |