151 lines
7.0 KiB
Go
151 lines
7.0 KiB
Go
package providers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var providerRequestIDInMessage = regexp.MustCompile(`(?i)\brequest[ _-]?id\s*[:=]\s*([A-Za-z0-9_.:-]{3,128})\b`)
|
|
|
|
// SanitizeFailureDetail retains a short provider explanation while removing
|
|
// submitted text, media links, credentials, and unrecognized quoted values.
|
|
// It is safe to call again after the original request has been discarded.
|
|
func SanitizeFailureDetail(message string, requestBody []byte, secrets ...string) string {
|
|
if strings.TrimSpace(message) == "" {
|
|
return ""
|
|
}
|
|
privateValues := append([]string(nil), secrets...)
|
|
var request any
|
|
if json.Unmarshal(requestBody, &request) == nil {
|
|
collectPrivateRequestValues(request, "", &privateValues)
|
|
}
|
|
for _, value := range privateValues {
|
|
if value == "" {
|
|
continue
|
|
}
|
|
message = strings.ReplaceAll(message, value, "[redacted]")
|
|
encoded, _ := json.Marshal(value)
|
|
if len(encoded) > 2 {
|
|
message = strings.ReplaceAll(message, string(encoded[1:len(encoded)-1]), "[redacted]")
|
|
}
|
|
}
|
|
message = minimaxDiagnosticURL.ReplaceAllString(message, "[redacted]")
|
|
message = minimaxDiagnosticCredential.ReplaceAllString(message, "[redacted]")
|
|
message = minimaxDiagnosticQuoted.ReplaceAllStringFunc(message, func(quoted string) string {
|
|
value := quoted[1 : len(quoted)-1]
|
|
if minimaxDiagnosticValue(value) != "unrecognized" {
|
|
return quoted
|
|
}
|
|
return "[redacted]"
|
|
})
|
|
message = minimaxDiagnosticLongToken.ReplaceAllString(message, "[redacted]")
|
|
message = strings.Join(strings.Fields(message), " ")
|
|
runes := []rune(message)
|
|
if len(runes) > 512 {
|
|
return string(runes[:512]) + "...[truncated]"
|
|
}
|
|
return message
|
|
}
|
|
|
|
func collectPrivateRequestValues(value any, key string, values *[]string) {
|
|
switch typed := value.(type) {
|
|
case map[string]any:
|
|
for childKey, childValue := range typed {
|
|
collectPrivateRequestValues(childValue, strings.ToLower(childKey), values)
|
|
}
|
|
case []any:
|
|
for _, child := range typed {
|
|
collectPrivateRequestValues(child, key, values)
|
|
}
|
|
case string:
|
|
if strings.Contains(key, "prompt") || strings.Contains(key, "text") || strings.Contains(key, "url") || strings.Contains(key, "image") || strings.Contains(key, "video") || strings.Contains(key, "audio") || strings.Contains(key, "token") || strings.Contains(key, "secret") || strings.Contains(key, "key") || strings.Contains(key, "signature") || strings.Contains(key, "password") || strings.Contains(key, "credential") {
|
|
*values = append(*values, typed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func requestIDFromMessage(message string) string {
|
|
match := providerRequestIDInMessage.FindStringSubmatch(message)
|
|
if len(match) < 2 {
|
|
return ""
|
|
}
|
|
return safeHTTPProviderDiagnosticToken(match[1], 128)
|
|
}
|
|
|
|
func safeProviderResponseDetail(raw, requestBody []byte, secrets ...string) string {
|
|
root := record(raw)
|
|
providerError := object(first(root["error"], root["Error"]))
|
|
output := object(root["output"])
|
|
data := object(root["data"])
|
|
task := object(first(root["task"], data["task"]))
|
|
nestedError := object(first(data["error"], task["error"], output["error"]))
|
|
metadataError := object(first(object(first(root["ResponseMetadata"], root["response_metadata"]))["Error"], object(first(root["ResponseMetadata"], root["response_metadata"]))["error"]))
|
|
message := stringValue(providerError["message"], providerError["Message"], nestedError["message"], nestedError["Message"], task["message"], output["message"], data["message"], metadataError["Message"], metadataError["message"], root["message"], root["Message"])
|
|
return SanitizeFailureDetail(message, requestBody, secrets...)
|
|
}
|
|
|
|
func successBusinessCode(code string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(code)) {
|
|
case "", "0", "200", "ok", "success", "10000":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Only explicit top-level or output error indicators count as a business
|
|
// rejection. A successful task can carry a normal informational message.
|
|
func inspectProviderBusinessRejection(raw []byte, headers http.Header, requestBody []byte, secrets ...string) (code, requestID, detail string, rejected bool) {
|
|
root := record(raw)
|
|
providerError := object(first(root["error"], root["Error"]))
|
|
output := object(root["output"])
|
|
data := object(root["data"])
|
|
task := object(first(root["task"], data["task"]))
|
|
nestedError := object(first(data["error"], task["error"], output["error"]))
|
|
code, requestID, _ = inspectHTTPProviderFailure(raw, headers)
|
|
message := stringValue(providerError["message"], providerError["Message"], nestedError["message"], nestedError["Message"], output["message"], data["message"], root["message"])
|
|
if (len(providerError) > 0 || len(nestedError) > 0) && (code != "" || message != "") {
|
|
rejected = true
|
|
} else if rootCode := safeHTTPProviderDiagnosticToken(first(root["code"], root["Code"]), 64); rootCode != "" && !successBusinessCode(rootCode) {
|
|
rejected = true
|
|
} else if outputCode := safeHTTPProviderDiagnosticToken(first(output["code"], output["Code"]), 64); outputCode != "" && !successBusinessCode(outputCode) {
|
|
rejected = true
|
|
} else if strings.EqualFold(stringValue(root["type"]), "error") && message != "" {
|
|
rejected = true
|
|
}
|
|
if !rejected {
|
|
return "", "", "", false
|
|
}
|
|
return code, requestID, SanitizeFailureDetail(message, requestBody, secrets...), true
|
|
}
|
|
|
|
func annotateTerminalFailure(result Result, raw []byte, headers http.Header, requestBody []byte, secrets ...string) Result {
|
|
if result.Status != StatusFailed && result.Status != StatusExpired && result.Status != StatusCancelled {
|
|
return result
|
|
}
|
|
root := record(raw)
|
|
business := object(first(root["Result"], root["result"]))
|
|
data := object(root["data"])
|
|
businessData := object(first(business["data"], business["Data"]))
|
|
output := object(root["output"])
|
|
task := object(root["task"])
|
|
providerError := object(first(task["error"], data["error"], businessData["error"], businessData["Error"], business["error"], business["Error"], output["error"], root["error"], root["Error"]))
|
|
code, requestID, _ := inspectHTTPProviderFailure(raw, headers)
|
|
if nestedCode := firstSafeHTTPProviderDiagnosticToken(64, providerError["code"], providerError["Code"], task["error_code"], data["error_code"], businessData["error_code"], business["error_code"]); nestedCode != "" {
|
|
code = nestedCode
|
|
}
|
|
if nestedID := firstSafeHTTPProviderDiagnosticToken(128, providerError["request_id"], providerError["requestId"], providerError["RequestId"], task["request_id"], data["request_id"], businessData["request_id"], business["request_id"]); nestedID != "" {
|
|
requestID = nestedID
|
|
}
|
|
message := stringValue(providerError["message"], providerError["Message"], task["error_message"], task["message"], output["message"], data["error_message"], data["message"], businessData["error_message"], businessData["message"], business["message"], root["message"], result.ErrorMessage)
|
|
if requestID == "" {
|
|
requestID = requestIDFromMessage(message)
|
|
}
|
|
result.ErrorCode = code
|
|
result.RequestID = requestID
|
|
result.ErrorDetail = SanitizeFailureDetail(message, requestBody, secrets...)
|
|
result.ErrorMessage = SanitizeFailureDetail(result.ErrorMessage, requestBody, secrets...)
|
|
return result
|
|
}
|