fix(upstream): vivid 上传会话宽容解析 + 错误回显原始响应
vivid 协议各实现的 /v1/uploads 响应形状不一(顶层 / data 包裹、 url|upload_url|presigned_url、key|file_key|object_key)。之前只认顶层 url+key,遇到包装或别名就报 "upload session missing url/key" 且看不 到上游实际返回。现在逐层(含嵌套 data/result/upload/session)搜寻两 个字段,仍缺失时把原始 JSON 截断进错误信息便于定位。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -189,10 +189,10 @@ func (c *Client) vividUploadRef(ctx context.Context, baseURL, apiKey string, dat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
putURL := strings.TrimSpace(stringValue(up["url"]))
|
putURL, key := vividUploadSession(up)
|
||||||
key := strings.TrimSpace(stringValue(up["key"]))
|
|
||||||
if putURL == "" || key == "" {
|
if putURL == "" || key == "" {
|
||||||
return "", fmt.Errorf("%w: upload session missing url/key", ErrTemporaryUpstream)
|
raw, _ := json.Marshal(up)
|
||||||
|
return "", fmt.Errorf("%w: upload session missing url/key (got %s)", ErrTemporaryUpstream, clip(raw, 200))
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(http.MethodPut, putURL, bytes.NewReader(data))
|
req, err := http.NewRequest(http.MethodPut, putURL, bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -212,6 +212,47 @@ func (c *Client) vividUploadRef(ctx context.Context, baseURL, apiKey string, dat
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vividUploadSession extracts the presigned PUT url + object key from a
|
||||||
|
// /v1/uploads response. Vivid-protocol implementations vary: some return the
|
||||||
|
// pair at top level, others wrap it in {"data":{...}} / {"result":{...}}, and
|
||||||
|
// field spellings differ (upload_url / presigned_url / put_url; key /
|
||||||
|
// file_key / object_key). Search top level first, then one layer deep.
|
||||||
|
func vividUploadSession(up map[string]any) (putURL, key string) {
|
||||||
|
urlKeys := []string{"url", "upload_url", "uploadUrl", "presigned_url", "presignedUrl", "put_url", "putUrl", "signed_url", "signedUrl"}
|
||||||
|
keyKeys := []string{"key", "file_key", "fileKey", "object_key", "objectKey", "upload_key", "uploadKey"}
|
||||||
|
pick := func(m map[string]any) (string, string) {
|
||||||
|
var u, k string
|
||||||
|
for _, f := range urlKeys {
|
||||||
|
if u = strings.TrimSpace(stringValue(m[f])); u != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range keyKeys {
|
||||||
|
if k = strings.TrimSpace(stringValue(m[f])); k != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u, k
|
||||||
|
}
|
||||||
|
levels := []map[string]any{up}
|
||||||
|
for _, nest := range []string{"data", "result", "upload", "session", "upload_session"} {
|
||||||
|
if sub, ok := up[nest].(map[string]any); ok {
|
||||||
|
levels = append(levels, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// url 和 key 允许跨层出现(比如 url 在顶层、key 在 data 里)。
|
||||||
|
for _, m := range levels {
|
||||||
|
u, k := pick(m)
|
||||||
|
if putURL == "" {
|
||||||
|
putURL = u
|
||||||
|
}
|
||||||
|
if key == "" {
|
||||||
|
key = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return putURL, key
|
||||||
|
}
|
||||||
|
|
||||||
// vividRefKindMime classifies a reference blob into the vivid "kind" (image /
|
// vividRefKindMime classifies a reference blob into the vivid "kind" (image /
|
||||||
// video / audio) plus a concrete mime. Magic bytes first (DetectContentType
|
// video / audio) plus a concrete mime. Magic bytes first (DetectContentType
|
||||||
// misses mov/m4a/ogg-audio), then sniffing, then a png default.
|
// misses mov/m4a/ogg-audio), then sniffing, then a png default.
|
||||||
|
|||||||
Reference in New Issue
Block a user