fix(provider): 修正媒体请求转换与上游错误透传

按上游协议能力延迟处理媒体资源:OpenAI 兼容平台默认使用 multipart,显式配置后才发送 JSON URL;Gemini 官方协议使用 Files API,兼容协议使用内嵌 Base64,并同步覆盖相关媒体客户端。\n\n安全的上游 400/422 原始错误会作为下游 message 返回,同时保留结构化诊断信息和历史任务兼容。\n\n验证:API 全量无缓存测试、go vet、pnpm lint、pnpm test、pnpm build、pnpm openapi、git diff --check。
This commit is contained in:
2026-08-05 00:40:42 +08:00
parent c79c2a7b44
commit ebdb96e7d7
19 changed files with 1163 additions and 93 deletions
+33 -10
View File
@@ -11,9 +11,7 @@ import (
func publicGatewayTask(task store.GatewayTask) store.GatewayTask {
if task.Status == "failed" || task.Status == "cancelled" || task.ErrorCode != "" || task.ErrorMessage != "" {
value := publicerror.FromFields(task.ErrorCode, firstNonEmpty(task.ErrorMessage, task.Error), taskErrorHTTPStatus(task), false)
if task.PublicError != nil && task.PublicError.Code != "" && task.PublicError.Source != "" {
value = *task.PublicError
}
value = mergeStoredPublicError(task.PublicError, value)
value = publicerror.WithIDs(value, task.RequestID, task.ID)
publicerror.Observe(value)
task.PublicError = &value
@@ -32,9 +30,7 @@ func publicGatewayTask(task store.GatewayTask) store.GatewayTask {
status = http.StatusBadGateway
}
value := publicerror.FromFields(attempt.ErrorCode, attempt.ErrorMessage, status, attempt.Retryable)
if attempt.PublicError != nil && attempt.PublicError.Code != "" && attempt.PublicError.Source != "" {
value = *attempt.PublicError
}
value = mergeStoredPublicError(attempt.PublicError, value)
value = publicerror.WithIDs(value, attempt.RequestID, task.ID)
attempt.PublicError = &value
attempt.ErrorCode = value.Code
@@ -44,14 +40,33 @@ func publicGatewayTask(task store.GatewayTask) store.GatewayTask {
}
func publicTaskError(task store.GatewayTask) publicerror.Error {
if task.PublicError != nil && task.PublicError.Code != "" && task.PublicError.Source != "" {
return publicerror.WithIDs(*task.PublicError, task.RequestID, task.ID)
}
status := taskErrorHTTPStatus(task)
if status <= 0 {
status = http.StatusBadGateway
}
return publicerror.WithIDs(publicerror.FromFields(task.ErrorCode, firstNonEmpty(task.ErrorMessage, task.Error, task.Message), status, false), task.RequestID, task.ID)
value := publicerror.FromFields(task.ErrorCode, firstNonEmpty(task.ErrorMessage, task.Error, task.Message), status, false)
value = mergeStoredPublicError(task.PublicError, value)
return publicerror.WithIDs(value, task.RequestID, task.ID)
}
func mergeStoredPublicError(stored *publicerror.Error, derived publicerror.Error) publicerror.Error {
if stored == nil || stored.Code == "" || stored.Source == "" {
return derived
}
value := *stored
if len(value.Details) == 0 && len(derived.Details) > 0 {
value.Details = derived.Details
}
if message := safeDerivedUpstreamMessage(derived); message != "" {
value.Message = message
}
return value
}
func safeDerivedUpstreamMessage(value publicerror.Error) string {
upstream, _ := value.Details["upstreamError"].(map[string]any)
message, _ := upstream["message"].(string)
return strings.TrimSpace(message)
}
func taskErrorHTTPStatus(task store.GatewayTask) int {
@@ -109,6 +124,9 @@ func publicErrorMap(value publicerror.Error) map[string]any {
if taskID := strings.TrimSpace(value.TaskID); taskID != "" {
out["taskId"] = taskID
}
if len(value.Details) > 0 {
out["details"] = value.Details
}
return out
}
@@ -146,6 +164,11 @@ func safePublicErrorDetails(details map[string]any, value publicerror.Error, inc
}
}
}
for key, item := range value.Details {
if item != nil {
out[key] = item
}
}
if includePublicError {
out["publicError"] = value
}