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:
2026-07-22 15:34:59 +08:00
parent 42e8b517fd
commit e07a997aa9
32 changed files with 2436 additions and 591 deletions
+44 -12
View File
@@ -80,7 +80,18 @@ func (c KelingClient) runVideo(ctx context.Context, request Request, token strin
}()
if upstreamTaskID == "" {
submitResult, requestID, err := c.postJSON(ctx, request, prepared.Endpoint, token, prepared.Payload)
if err := notifySubmissionStarted(request); err != nil {
return Response{}, err
}
submitResult, requestID, submitWire, err := c.postJSON(ctx, request, prepared.Endpoint, token, prepared.Payload)
if notifyErr := notifyWireResponse(request, submitWire); notifyErr != nil {
return Response{}, notifyErr
}
if err == nil || ErrorResponseMetadata(err).StatusCode > 0 {
if notifyErr := notifyResponseReceived(request); notifyErr != nil {
return Response{}, notifyErr
}
}
submitRequestID = requestID
if err != nil {
return Response{}, annotateResponseError(err, submitRequestID, submitStartedAt, time.Now())
@@ -122,6 +133,11 @@ func (c KelingClient) runVideo(ctx context.Context, request Request, token strin
return Response{}, annotateResponseError(err, requestID, pollStartedAt, pollFinishedAt)
}
lastResult = pollResult
if request.OnRemoteTaskPolled != nil {
if err := request.OnRemoteTaskPolled(upstreamTaskID, pollResult); err != nil {
return Response{}, err
}
}
switch kelingTaskStatus(pollResult) {
case "succeed":
@@ -176,7 +192,18 @@ func (c KelingClient) runTaskAPIVideo(ctx context.Context, request Request, toke
if err != nil {
return Response{}, err
}
submitResult, requestID, err := c.postJSONAt(ctx, request, taskAPIBaseURL, endpoint, token, payload)
if err := notifySubmissionStarted(request); err != nil {
return Response{}, err
}
submitResult, requestID, submitWire, err := c.postJSONAt(ctx, request, taskAPIBaseURL, endpoint, token, payload, ProtocolKlingV2Omni)
if notifyErr := notifyWireResponse(request, submitWire); notifyErr != nil {
return Response{}, notifyErr
}
if err == nil || ErrorResponseMetadata(err).StatusCode > 0 {
if notifyErr := notifyResponseReceived(request); notifyErr != nil {
return Response{}, notifyErr
}
}
submitRequestID = requestID
if err != nil {
return Response{}, annotateResponseError(err, submitRequestID, submitStartedAt, time.Now())
@@ -224,6 +251,11 @@ func (c KelingClient) runTaskAPIVideo(ctx context.Context, request Request, toke
if err != nil {
return Response{}, annotateResponseError(err, requestID, pollStartedAt, pollFinishedAt)
}
if request.OnRemoteTaskPolled != nil {
if err := request.OnRemoteTaskPolled(upstreamTaskID, pollResult); err != nil {
return Response{}, err
}
}
task := kelingTaskAPITask(pollResult, upstreamTaskID)
lastStatus = strings.ToLower(strings.TrimSpace(stringFromAny(task["status"])))
@@ -567,31 +599,31 @@ func (c KelingClient) kelingOmniElementList(ctx context.Context, request Request
return elements, createdIDs, nil
}
func (c KelingClient) postJSON(ctx context.Context, request Request, path string, token string, body map[string]any) (map[string]any, string, error) {
return c.postJSONAt(ctx, request, request.Candidate.BaseURL, path, token, body)
func (c KelingClient) postJSON(ctx context.Context, request Request, path string, token string, body map[string]any) (map[string]any, string, *WireResponse, error) {
return c.postJSONAt(ctx, request, request.Candidate.BaseURL, path, token, body, ProtocolKlingV1Omni)
}
func (c KelingClient) postJSONAt(ctx context.Context, request Request, baseURL string, path string, token string, body map[string]any) (map[string]any, string, error) {
func (c KelingClient) postJSONAt(ctx context.Context, request Request, baseURL string, path string, token string, body map[string]any, protocol string) (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 "+token)
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, protocol)
if err != nil {
return result, requestID, err
return result, requestID, wire, err
}
if code := intFromAny(result["code"]); code != 0 {
return result, requestID, &ClientError{Code: kelingEnvelopeErrorCode(result), Message: kelingEnvelopeErrorMessage(result), RequestID: firstNonEmpty(requestID, stringFromAny(result["request_id"])), Retryable: false}
return result, requestID, wire, &ClientError{Code: kelingEnvelopeErrorCode(result), Message: kelingEnvelopeErrorMessage(result), RequestID: firstNonEmpty(requestID, stringFromAny(result["request_id"])), Retryable: false, Wire: wire}
}
return result, firstNonEmpty(requestID, stringFromAny(result["request_id"])), nil
return result, firstNonEmpty(requestID, stringFromAny(result["request_id"])), wire, nil
}
func (c KelingClient) getJSON(ctx context.Context, request Request, path string, token string) (map[string]any, string, error) {
@@ -663,7 +695,7 @@ func (c KelingClient) cleanupKelingElements(ctx context.Context, request Request
if id == "" {
continue
}
_, _, _ = c.postJSON(ctx, request, "/general/delete-elements", token, map[string]any{"element_id": id})
_, _, _, _ = c.postJSON(ctx, request, "/general/delete-elements", token, map[string]any{"element_id": id})
}
return nil
}