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:
@@ -25,7 +25,7 @@ const (
|
||||
|
||||
func (s *Server) registerKlingCompatibilityRoutes(mux *http.ServeMux) {
|
||||
handler := func(next http.HandlerFunc) http.Handler {
|
||||
return s.requireUser(auth.PermissionBasic, http.HandlerFunc(next))
|
||||
return s.requireProtocolUser(clients.ProtocolKlingV1Omni, http.HandlerFunc(next))
|
||||
}
|
||||
// /api/v1 is the canonical public prefix. The historical /kling paths
|
||||
// remain registered below so existing clients can migrate without downtime.
|
||||
@@ -60,7 +60,7 @@ func (s *Server) registerKlingCompatibilityRoutes(mux *http.ServeMux) {
|
||||
// @Param input body map[string]interface{} true "可灵 V1 Omni 请求"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]interface{}
|
||||
// @Failure 401 {object} ErrorEnvelope
|
||||
// @Failure 401 {object} KlingErrorEnvelope
|
||||
// @Router /api/v1/kling/v1/videos/omni-video [post]
|
||||
func (s *Server) klingV1CreateOmniVideo(w http.ResponseWriter, r *http.Request) {
|
||||
var native map[string]any
|
||||
@@ -86,7 +86,7 @@ func (s *Server) klingV1CreateOmniVideo(w http.ResponseWriter, r *http.Request)
|
||||
// @Param input body map[string]interface{} true "可灵 API 2.0 请求"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]interface{}
|
||||
// @Failure 401 {object} ErrorEnvelope
|
||||
// @Failure 401 {object} KlingErrorEnvelope
|
||||
// @Router /api/v1/kling/v2/omni-video/{model} [post]
|
||||
func (s *Server) klingV2CreateOmniVideo(w http.ResponseWriter, r *http.Request) {
|
||||
model, ok := klingV2ProviderModel(r.PathValue("model"))
|
||||
@@ -151,11 +151,28 @@ func (s *Server) createKlingCompatTask(w http.ResponseWriter, r *http.Request, v
|
||||
return
|
||||
}
|
||||
task := created.Task
|
||||
targetProtocol := clients.ProtocolKlingV1Omni
|
||||
if version == "v2" {
|
||||
targetProtocol = clients.ProtocolKlingV2Omni
|
||||
}
|
||||
if !created.Replayed {
|
||||
if err := s.store.SetTaskCompatibilitySubmission(r.Context(), task.ID, store.CompatibilitySubmission{TargetProtocol: targetProtocol, PublicID: task.ID}); err != nil {
|
||||
writeKlingCompatError(w, http.StatusInternalServerError, "create compatibility metadata failed", "task_create_failed")
|
||||
return
|
||||
}
|
||||
if err := s.runner.EnqueueAsyncTask(r.Context(), task); err != nil {
|
||||
writeKlingCompatError(w, http.StatusInternalServerError, err.Error(), "enqueue_failed")
|
||||
return
|
||||
}
|
||||
task, err = s.waitForCompatibilitySubmission(r, task)
|
||||
if err != nil {
|
||||
if wire := clients.ErrorWireResponse(err); wireResponseMatches(wire, targetProtocol) {
|
||||
writeWireResponse(w, wire)
|
||||
return
|
||||
}
|
||||
writeKlingCompatError(w, statusFromRunError(err), err.Error(), clients.ErrorCode(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
w.Header().Set("Idempotent-Replayed", "true")
|
||||
}
|
||||
@@ -181,6 +198,11 @@ func klingCompatTaskBody(version string, model string, native map[string]any) (m
|
||||
body["modelType"] = "omni_video"
|
||||
body["_compat_provider"] = klingCompatProvider
|
||||
body["_kling_compat_version"] = version
|
||||
if version == "v2" {
|
||||
body["_gateway_target_protocol"] = clients.ProtocolKlingV2Omni
|
||||
} else {
|
||||
body["_gateway_target_protocol"] = clients.ProtocolKlingV1Omni
|
||||
}
|
||||
body["content"] = klingLegacyContent(body)
|
||||
mode := strings.TrimSpace(stringFromRequestAny(body["mode"]))
|
||||
if mode == "" && version == "v1" {
|
||||
@@ -590,12 +612,23 @@ func (s *Server) klingV2ListTasks(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func klingV1Envelope(task store.GatewayTask) map[string]any {
|
||||
if klingTaskUsesNativeV1Protocol(task) {
|
||||
if raw := klingMap(task.Result["raw"]); len(raw) > 0 {
|
||||
return raw
|
||||
}
|
||||
if len(task.RemoteTaskPayload) > 0 {
|
||||
return task.RemoteTaskPayload
|
||||
}
|
||||
if raw := klingMap(task.CompatibilitySubmitBody); len(raw) > 0 {
|
||||
return raw
|
||||
}
|
||||
}
|
||||
return map[string]any{"code": 0, "message": "success", "request_id": firstNonEmpty(task.RequestID, task.ID), "data": klingV1TaskData(task)}
|
||||
}
|
||||
|
||||
func klingV1TaskData(task store.GatewayTask) map[string]any {
|
||||
data := map[string]any{
|
||||
"task_id": task.ID, "task_status": klingV1Status(task.Status),
|
||||
"task_id": klingCompatibilityPublicID(task), "task_status": klingV1Status(task.Status),
|
||||
"task_info": map[string]any{"external_task_id": task.ExternalTaskID},
|
||||
"created_at": task.CreatedAt.UnixMilli(), "updated_at": task.UpdatedAt.UnixMilli(),
|
||||
}
|
||||
@@ -613,12 +646,23 @@ func klingV1TaskData(task store.GatewayTask) map[string]any {
|
||||
}
|
||||
|
||||
func klingV2Envelope(task store.GatewayTask) map[string]any {
|
||||
if task.CompatibilityProtocol == clients.ProtocolKlingV2Omni && task.CompatibilitySourceProtocol == clients.ProtocolKlingV2Omni {
|
||||
if raw := klingMap(task.Result["raw"]); len(raw) > 0 {
|
||||
return raw
|
||||
}
|
||||
if len(task.RemoteTaskPayload) > 0 {
|
||||
return task.RemoteTaskPayload
|
||||
}
|
||||
if len(task.CompatibilitySubmitBody) > 0 {
|
||||
return task.CompatibilitySubmitBody
|
||||
}
|
||||
}
|
||||
return map[string]any{"code": 0, "message": "success", "request_id": firstNonEmpty(task.RequestID, task.ID), "data": klingV2TaskData(task)}
|
||||
}
|
||||
|
||||
func klingV2TaskData(task store.GatewayTask) map[string]any {
|
||||
data := map[string]any{
|
||||
"id": task.ID, "status": klingV2Status(task.Status),
|
||||
"id": klingCompatibilityPublicID(task), "status": klingV2Status(task.Status),
|
||||
"create_time": task.CreatedAt.UnixMilli(), "update_time": task.UpdatedAt.UnixMilli(),
|
||||
"external_id": task.ExternalTaskID,
|
||||
}
|
||||
@@ -631,6 +675,22 @@ func klingV2TaskData(task store.GatewayTask) map[string]any {
|
||||
return data
|
||||
}
|
||||
|
||||
func klingCompatibilityPublicID(task store.GatewayTask) string {
|
||||
if publicID := strings.TrimSpace(task.CompatibilityPublicID); publicID != "" {
|
||||
return publicID
|
||||
}
|
||||
return task.ID
|
||||
}
|
||||
|
||||
func klingTaskUsesNativeV1Protocol(task store.GatewayTask) bool {
|
||||
return task.CompatibilityProtocol == clients.ProtocolKlingV1Omni && task.CompatibilitySourceProtocol == clients.ProtocolKlingV1Omni
|
||||
}
|
||||
|
||||
func klingMap(value any) map[string]any {
|
||||
result, _ := value.(map[string]any)
|
||||
return result
|
||||
}
|
||||
|
||||
func klingTaskVideos(task store.GatewayTask) []any {
|
||||
items, _ := task.Result["data"].([]any)
|
||||
videos := make([]any, 0, len(items))
|
||||
|
||||
Reference in New Issue
Block a user