feat(api): 统一官方兼容接口响应协议
兼容接口现在以入口协议作为最终响应协议,同协议保留官方 Wire 响应,跨协议统一转换成功、任务状态与错误结构。 同时修正异步提交状态边界,持久化兼容公开任务标识和官方提交响应,并新增迁移、流式响应及协议契约测试。 验证:go vet ./...;go test ./...;govulncheck ./...;pnpm lint;pnpm test;pnpm build;pnpm audit --audit-level high;pnpm openapi;全部 CI 脚本。
This commit is contained in:
@@ -46,12 +46,18 @@ func (c VolcesClient) runImage(ctx context.Context, request Request, apiKey stri
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
|
||||
responseStartedAt := time.Now()
|
||||
if err := notifySubmissionStarted(request); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
resp, err := httpClient(request.HTTPClient, c.HTTPClient).Do(req)
|
||||
if err != nil {
|
||||
return Response{}, &ClientError{Code: "network", Message: err.Error(), Retryable: true}
|
||||
}
|
||||
if err := notifyResponseReceived(request); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
requestID := requestIDFromHTTPResponse(resp)
|
||||
result, err := decodeHTTPResponse(resp)
|
||||
result, wire, err := decodeHTTPResponseForProtocol(resp, ProtocolOpenAIImages)
|
||||
responseFinishedAt := time.Now()
|
||||
if err != nil {
|
||||
return Response{}, annotateResponseError(err, requestID, responseStartedAt, responseFinishedAt)
|
||||
@@ -67,6 +73,8 @@ func (c VolcesClient) runImage(ctx context.Context, request Request, apiKey stri
|
||||
ResponseStartedAt: responseStartedAt,
|
||||
ResponseFinishedAt: responseFinishedAt,
|
||||
ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
|
||||
UpstreamProtocol: ProtocolOpenAIImages,
|
||||
Wire: wire,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -80,7 +88,7 @@ func (c VolcesClient) runVideo(ctx context.Context, request Request, apiKey stri
|
||||
if err := validateVolcesVideoTaskBody(body); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
submitResult, requestID, err := c.postJSON(ctx, request, request.Candidate.BaseURL, taskPath, apiKey, body)
|
||||
submitResult, requestID, _, err := c.postJSON(ctx, request, request.Candidate.BaseURL, taskPath, apiKey, body)
|
||||
submitRequestID = requestID
|
||||
if err != nil {
|
||||
return Response{}, annotateResponseError(err, submitRequestID, submitStartedAt, time.Now())
|
||||
@@ -119,7 +127,7 @@ func (c VolcesClient) runVideo(ctx context.Context, request Request, apiKey stri
|
||||
}
|
||||
case <-nextPoll.C:
|
||||
pollStartedAt := time.Now()
|
||||
pollResult, pollRequestID, err := c.getJSON(ctx, request, request.Candidate.BaseURL, taskPath+"/"+upstreamTaskID, apiKey)
|
||||
pollResult, pollRequestID, pollWire, err := c.getJSON(ctx, request, request.Candidate.BaseURL, taskPath+"/"+upstreamTaskID, apiKey)
|
||||
pollFinishedAt := time.Now()
|
||||
requestID := firstNonEmpty(pollRequestID, submitRequestID, upstreamTaskID)
|
||||
lastRequestID = requestID
|
||||
@@ -151,6 +159,8 @@ func (c VolcesClient) runVideo(ctx context.Context, request Request, apiKey stri
|
||||
ResponseStartedAt: submitStartedAt,
|
||||
ResponseFinishedAt: pollFinishedAt,
|
||||
ResponseDurationMS: responseDurationMS(submitStartedAt, pollFinishedAt),
|
||||
UpstreamProtocol: ProtocolVolcesContents,
|
||||
Wire: pollWire,
|
||||
}, nil
|
||||
case "failed", "cancelled":
|
||||
return Response{}, &ClientError{
|
||||
@@ -214,44 +224,73 @@ func volcesVideoTaskPath(request Request) string {
|
||||
return strings.TrimRight(path, "/")
|
||||
}
|
||||
|
||||
func (c VolcesClient) postJSON(ctx context.Context, request Request, baseURL string, path string, apiKey string, body map[string]any) (map[string]any, string, error) {
|
||||
func (c VolcesClient) postJSON(ctx context.Context, request Request, baseURL string, path string, apiKey string, body map[string]any) (map[string]any, string, *WireResponse, error) {
|
||||
raw, _ := json.Marshal(body)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, joinURL(baseURL, path), bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, "", nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
if err := notifySubmissionStarted(request); err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
resp, err := httpClient(request.HTTPClient, c.HTTPClient).Do(req)
|
||||
if err != nil {
|
||||
return nil, "", &ClientError{Code: "network", Message: err.Error(), Retryable: true}
|
||||
return nil, "", nil, &ClientError{Code: "network", Message: err.Error(), Retryable: true}
|
||||
}
|
||||
if err := notifyResponseReceived(request); err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
requestID := requestIDFromHTTPResponse(resp)
|
||||
result, err := decodeHTTPResponse(resp)
|
||||
result, wire, err := decodeHTTPResponseForProtocol(resp, ProtocolVolcesContents)
|
||||
if err != nil {
|
||||
return result, requestID, err
|
||||
if notifyErr := notifyWireResponse(request, wire); notifyErr != nil {
|
||||
return result, requestID, wire, notifyErr
|
||||
}
|
||||
return result, requestID, wire, err
|
||||
}
|
||||
original := result
|
||||
result, envelopeRequestID, err := normalizeVolcesCompatibleResult(result)
|
||||
return result, firstNonEmpty(requestID, envelopeRequestID), err
|
||||
if wire != nil {
|
||||
wire.Converted = volcesCompatibleResultConverted(original)
|
||||
}
|
||||
if notifyErr := notifyWireResponse(request, wire); notifyErr != nil {
|
||||
return result, firstNonEmpty(requestID, envelopeRequestID), wire, notifyErr
|
||||
}
|
||||
return result, firstNonEmpty(requestID, envelopeRequestID), wire, err
|
||||
}
|
||||
|
||||
func (c VolcesClient) getJSON(ctx context.Context, request Request, baseURL string, path string, apiKey string) (map[string]any, string, error) {
|
||||
func (c VolcesClient) getJSON(ctx context.Context, request Request, baseURL string, path string, apiKey string) (map[string]any, string, *WireResponse, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, joinURL(baseURL, path), nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, "", nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
resp, err := httpClient(request.HTTPClient, c.HTTPClient).Do(req)
|
||||
if err != nil {
|
||||
return nil, "", &ClientError{Code: "network", Message: err.Error(), Retryable: true}
|
||||
return nil, "", nil, &ClientError{Code: "network", Message: err.Error(), Retryable: true}
|
||||
}
|
||||
requestID := requestIDFromHTTPResponse(resp)
|
||||
result, err := decodeHTTPResponse(resp)
|
||||
result, wire, err := decodeHTTPResponseForProtocol(resp, ProtocolVolcesContents)
|
||||
if err != nil {
|
||||
return result, requestID, err
|
||||
return result, requestID, wire, err
|
||||
}
|
||||
original := result
|
||||
result, envelopeRequestID, err := normalizeVolcesCompatibleResult(result)
|
||||
return result, firstNonEmpty(requestID, envelopeRequestID), err
|
||||
if wire != nil {
|
||||
wire.Converted = volcesCompatibleResultConverted(original)
|
||||
}
|
||||
return result, firstNonEmpty(requestID, envelopeRequestID), wire, err
|
||||
}
|
||||
|
||||
func volcesCompatibleResultConverted(result map[string]any) bool {
|
||||
if _, ok := result["error"].(map[string]any); ok {
|
||||
return true
|
||||
}
|
||||
_, hasCode := result["code"]
|
||||
_, hasData := result["data"].(map[string]any)
|
||||
return hasCode && hasData
|
||||
}
|
||||
|
||||
func normalizeVolcesCompatibleResult(result map[string]any) (map[string]any, string, error) {
|
||||
@@ -354,6 +393,10 @@ func cleanProviderBody(body map[string]any) map[string]any {
|
||||
"poll_interval_ms",
|
||||
"pollTimeoutSeconds",
|
||||
"poll_timeout_seconds",
|
||||
"_gateway_compatibility",
|
||||
"_gateway_target_protocol",
|
||||
"_compat_provider",
|
||||
"_kling_compat_version",
|
||||
} {
|
||||
delete(out, key)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user