fix(runner): 修复转存后残留二进制误判
统一结果二进制探测与上传规则,避免将 thinking_bytes 等上游元数据误判为待转存媒体,同时保留显式媒体字段和签名识别。\n\n补充不含原始内容的安全诊断,并兼容对象存储前缀旧字段;修正真实 OSS 验收脚本使用的正式字段。\n\n验证:Go 全量测试、go vet、迁移安全检查、真实 Gemini 上游响应及阿里云 OSS 转存均通过。
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -106,6 +107,52 @@ func TaskResultHasInlineBinary(result map[string]any) bool {
|
||||
return localBinaryValueHasPayload(result, "", nil, 0)
|
||||
}
|
||||
|
||||
func taskResultInlineBinaryDiagnostics(result map[string]any) []string {
|
||||
diagnostics := make([]string, 0, 4)
|
||||
appendInlineBinaryDiagnostics(result, "", nil, "$", 0, &diagnostics)
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
func appendInlineBinaryDiagnostics(value any, key string, siblings map[string]any, path string, depth int, diagnostics *[]string) {
|
||||
if depth >= localBinaryMaxDepth || len(*diagnostics) >= 8 {
|
||||
return
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
if payload, contentType, ok := localBufferObjectBytes(typed); ok {
|
||||
*diagnostics = append(*diagnostics, fmt.Sprintf("%s buffer bytes=%d contentType=%s", path, len(payload), contentType))
|
||||
return
|
||||
}
|
||||
keys := make([]string, 0, len(typed))
|
||||
for childKey := range typed {
|
||||
keys = append(keys, childKey)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, childKey := range keys {
|
||||
appendInlineBinaryDiagnostics(typed[childKey], childKey, typed, path+"."+childKey, depth+1, diagnostics)
|
||||
}
|
||||
case []any:
|
||||
if localBinaryKey(key) {
|
||||
if payload, ok := bytesFromNumberArray(typed); ok {
|
||||
*diagnostics = append(*diagnostics, fmt.Sprintf("%s number-array bytes=%d", path, len(payload)))
|
||||
return
|
||||
}
|
||||
}
|
||||
for index, child := range typed {
|
||||
appendInlineBinaryDiagnostics(child, key, siblings, fmt.Sprintf("%s[%d]", path, index), depth+1, diagnostics)
|
||||
}
|
||||
case []byte:
|
||||
if len(typed) > 0 {
|
||||
*diagnostics = append(*diagnostics, fmt.Sprintf("%s bytes=%d", path, len(typed)))
|
||||
}
|
||||
case string:
|
||||
payload, contentType, encoding, ok := localBinaryStringBytes(key, typed, siblings)
|
||||
if ok {
|
||||
*diagnostics = append(*diagnostics, fmt.Sprintf("%s string bytes=%d contentType=%s encoding=%s", path, len(payload), contentType, encoding))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func localBinaryValueHasPayload(value any, key string, siblings map[string]any, depth int) bool {
|
||||
if depth >= localBinaryMaxDepth {
|
||||
return false
|
||||
@@ -518,7 +565,12 @@ func localBinaryStringBytes(key string, value string, siblings map[string]any) (
|
||||
return nil, "", "", false
|
||||
}
|
||||
contentType := firstNonEmptyString(mediaContentTypeFromItem(siblings), defaultContentTypeForRawMediaKey(key))
|
||||
if !strict && contentType == "" {
|
||||
// Keys such as thinking_bytes and signature_buffer can carry opaque provider
|
||||
// metadata. Treat them as generated media only when a sibling content type or
|
||||
// the payload signature proves that they are media/document bytes. Explicit
|
||||
// Base64 media keys (b64_json, image_data, binary_data_base64, ...) retain the
|
||||
// strict behavior expected by compatible image protocols.
|
||||
if contentType == "" && (!strict || !generatedRawDataMediaPayloadKey(key)) {
|
||||
contentType = detectGeneratedAssetContentType(payload)
|
||||
if !generatedContentTypeIsMedia(contentType) && !generatedContentTypeIsDocument(contentType) {
|
||||
return nil, "", "", false
|
||||
|
||||
Reference in New Issue
Block a user