This commit is contained in:
2026-08-17 21:20:45 +02:00
parent 321f11e817
commit 6fcdc74843
157 changed files with 2895 additions and 7544 deletions
+94 -7
View File
@@ -67,11 +67,17 @@ func truncateRunes(s string, max int) string {
return s
}
const (
publicErrProcessingFailed = "processing_failed"
publicErrProviderUnavailable = "provider_unavailable"
)
// TruncateError returns a safe, short error string for DB storage / API clients.
// Secret-like substrings and logredact matches become an opaque message so
// job step_progress notes and v1 item errors cannot leak keys/JWTs/DSNs/emails.
// Common provider transport failures are rewritten to short user-facing text
// (no dial URLs / Go net strings) while preserving unrelated provider messages.
// (no dial URLs / Go net strings). Remaining provider internals map to stable
// public codes (processing_failed / provider_unavailable).
func TruncateError(err error) string {
if err == nil {
return ""
@@ -94,20 +100,101 @@ func TruncateError(err error) string {
return friendly
}
// Drop retry-exhaustion wrapper when the inner message is already clear.
cleaned := redacted
cleaned := stripOpenAIRetryPrefix(redacted)
if friendly := classifyProviderError(cleaned); friendly != "" {
return friendly
}
if code := mapPublicErrorCode(cleaned); code != "" {
return code
}
return truncateRunes(cleaned, 500)
}
// PublicV1Error maps a stored job/item error to a stable public v1 string.
// Dashboard notes / step_progress may still carry operator detail; public JSON must not.
func PublicV1Error(msg string) string {
msg = strings.TrimSpace(msg)
if msg == "" {
return ""
}
switch msg {
case publicErrProcessingFailed, publicErrProviderUnavailable,
"Product data not available", "Processing failed",
"provider error (details redacted)":
return msg
}
if strings.HasPrefix(msg, "processing.job.error.") {
return msg
}
if strings.Contains(strings.ToLower(msg), "insufficient credits") {
return msg
}
if friendly := classifyProviderError(msg); friendly != "" {
return friendly
}
cleaned := stripOpenAIRetryPrefix(msg)
if friendly := classifyProviderError(cleaned); friendly != "" {
return friendly
}
if code := mapPublicErrorCode(cleaned); code != "" {
return code
}
if looksLikeInternalProviderError(cleaned) {
return publicErrProcessingFailed
}
return truncateRunes(cleaned, 500)
}
func stripOpenAIRetryPrefix(msg string) string {
cleaned := msg
for _, prefix := range []string{
"openai retries exhausted: ",
"openai embedding retries exhausted: ",
} {
if strings.HasPrefix(strings.ToLower(cleaned), prefix) {
cleaned = strings.TrimSpace(cleaned[len(prefix):])
break
return strings.TrimSpace(cleaned[len(prefix):])
}
}
if friendly := classifyProviderError(cleaned); friendly != "" {
return friendly
return cleaned
}
func mapPublicErrorCode(msg string) string {
lower := strings.ToLower(strings.TrimSpace(msg))
if lower == "" {
return ""
}
return truncateRunes(cleaned, 500)
switch {
case strings.Contains(lower, "max_tokens"),
strings.Contains(lower, "length-capped"):
return publicErrProcessingFailed
case strings.Contains(lower, "openai unset"),
strings.Contains(lower, "platform openai"):
return publicErrProviderUnavailable
case strings.Contains(lower, "ai_enhance: skipped"):
return publicErrProcessingFailed
case strings.Contains(lower, "unavailable"),
strings.Contains(lower, "http 503"),
strings.Contains(lower, "overloaded"):
return publicErrProviderUnavailable
case strings.Contains(lower, "openai"),
strings.Contains(lower, "gpt-"),
strings.Contains(lower, " for model"):
return publicErrProcessingFailed
}
return ""
}
func looksLikeInternalProviderError(msg string) bool {
lower := strings.ToLower(msg)
for _, n := range []string{
"openai", "max_tokens", "length-capped", "gpt-", "ai_enhance",
"finish_reason", "chat/completions",
} {
if strings.Contains(lower, n) {
return true
}
}
return false
}
// classifyProviderError maps common OpenAI-compatible transport/auth failures