show specific AI provider errors instead of a generic message

This commit is contained in:
Abhinav Raut
2026-07-27 02:26:33 +05:30
parent e8e2419705
commit 8db2e34ab5
4 changed files with 29 additions and 10 deletions
+3 -3
View File
@@ -293,7 +293,7 @@ func handleAIGenerateReply(r *fastglue.Request) error {
return sendErrorEnvelope(r, err)
}
if strings.TrimSpace(resp) == "" {
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("globals.messages.somethingWentWrong"), nil))
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("ai.emptyResponse"), nil))
}
return r.SendEnvelope(resp)
}
@@ -322,7 +322,7 @@ func handleAISummarizeConversation(r *fastglue.Request) error {
return sendErrorEnvelope(r, err)
}
if strings.TrimSpace(summary) == "" {
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("globals.messages.somethingWentWrong"), nil))
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("ai.emptyResponse"), nil))
}
auser := r.RequestCtx.UserValue("user").(amodels.User)
note := "**" + app.i18n.T("ai.summaryNoteTitle") + "**\n\n" + summary
@@ -421,7 +421,7 @@ func handleAICopilot(r *fastglue.Request) error {
return sendErrorEnvelope(r, err)
}
if strings.TrimSpace(resp) == "" {
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("globals.messages.somethingWentWrong"), nil))
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("ai.emptyResponse"), nil))
}
// Copilot answers in markdown; store and return HTML so the panel, reply editor and private notes consume it as is.
resp = stringutil.Markdown2HTML(resp)
+6 -1
View File
@@ -552,7 +552,12 @@
"agent.generateApiKey": "Generate API key",
"agent.new": "New agent",
"agent.selectAvailabilityStatus": "Select availability status",
"ai.apiKeyNotSet": "The AI provider API key is not set. Please ask your administrator to set it up",
"ai.apiKeyNotSet": "The AI provider API key is not set. Ask your administrator to add it in AI settings.",
"ai.invalidAPIKey": "The AI provider rejected the API key. AI features won't work until your administrator updates it in AI settings.",
"ai.rateLimited": "The AI provider is rate limiting requests. Wait a minute and try again.",
"ai.providerUnavailable": "The AI provider could not be reached. Try again in a few minutes, or ask your administrator to check the AI settings.",
"ai.providerTimeout": "The AI provider took too long to respond. Try again, or with a shorter request.",
"ai.emptyResponse": "The AI returned an empty response. Nothing was generated. Try again, or rephrase your request.",
"ai.faqAlreadyReviewed": "This suggestion has already been reviewed.",
"ai.noAnswerWithinSteps": "The AI hit its step limit before answering. Try a simpler request, or ask an admin to raise the limit.",
"ai.agent.handoffDefault": "The AI assistant handed this conversation off to a human.",
+17 -3
View File
@@ -34,8 +34,10 @@ var (
//go:embed queries.sql
efs embed.FS
ErrInvalidAPIKey = errors.New("invalid API Key")
ErrApiKeyNotSet = errors.New("api Key not set")
ErrInvalidAPIKey = errors.New("invalid API Key")
ErrApiKeyNotSet = errors.New("api Key not set")
ErrRateLimited = errors.New("rate limited by AI provider")
ErrProviderUnavailable = errors.New("AI provider unavailable")
)
type Manager struct {
@@ -382,11 +384,23 @@ func (m *Manager) getProviderClient(providerType string) (ProviderClient, error)
func (m *Manager) providerError(err error) error {
if errors.Is(err, ErrInvalidAPIKey) {
m.lo.Error("invalid provider API key")
return envelope.NewError(envelope.InputError, m.i18n.T("globals.messages.somethingWentWrong"), nil)
return envelope.NewError(envelope.InputError, m.i18n.T("ai.invalidAPIKey"), nil)
}
if errors.Is(err, ErrApiKeyNotSet) {
return envelope.NewError(envelope.InputError, m.i18n.T("ai.apiKeyNotSet"), nil)
}
if errors.Is(err, ErrRateLimited) {
m.lo.Error("rate limited by AI provider", "error", err)
return envelope.NewError(envelope.RateLimitError, m.i18n.T("ai.rateLimited"), nil)
}
if errors.Is(err, context.DeadlineExceeded) {
m.lo.Error("AI provider request timed out", "error", err)
return envelope.NewError(envelope.GeneralError, m.i18n.T("ai.providerTimeout"), nil)
}
if errors.Is(err, ErrProviderUnavailable) {
m.lo.Error("AI provider unavailable", "error", err)
return envelope.NewError(envelope.GeneralError, m.i18n.T("ai.providerUnavailable"), nil)
}
m.lo.Error("error from AI provider", "error", err)
return envelope.NewError(envelope.GeneralError, capProviderErrorMessage(err), nil)
}
+3 -3
View File
@@ -351,7 +351,7 @@ func (o *OpenAIClient) doRequest(ctx context.Context, path string, bodyBytes []b
resp, err := o.client.Do(req)
if err != nil {
o.lo.Error("error making request to AI provider", "error", err)
return nil, 0, true, fmt.Errorf("making HTTP request: %w", err)
return nil, 0, true, fmt.Errorf("%w: making HTTP request: %w", ErrProviderUnavailable, err)
}
defer resp.Body.Close()
@@ -368,10 +368,10 @@ func (o *OpenAIClient) doRequest(ctx context.Context, path string, bodyBytes []b
return nil, 0, false, ErrInvalidAPIKey
case resp.StatusCode == http.StatusTooManyRequests:
o.lo.Error("rate limited by AI provider (429)", "response", string(respBytes))
return nil, parseRetryAfter(resp.Header.Get("Retry-After")), true, fmt.Errorf("provider API error: status %d: %s", resp.StatusCode, string(respBytes))
return nil, parseRetryAfter(resp.Header.Get("Retry-After")), true, fmt.Errorf("%w: status %d: %s", ErrRateLimited, resp.StatusCode, string(respBytes))
case resp.StatusCode >= 500:
o.lo.Error("server error from AI provider", "status", resp.StatusCode, "response", string(respBytes))
return nil, 0, true, fmt.Errorf("provider API error: status %d: %s", resp.StatusCode, string(respBytes))
return nil, 0, true, fmt.Errorf("%w: status %d: %s", ErrProviderUnavailable, resp.StatusCode, string(respBytes))
default:
o.lo.Error("non-ok response from AI provider", "status", resp.StatusCode, "response", string(respBytes))
return respBytes, 0, false, fmt.Errorf("provider API error: status %d: %s", resp.StatusCode, string(respBytes))