package httpapi import ( "bytes" "context" "encoding/json" "errors" "io" "net/http" "strings" "time" "data-terminal-cloud/internal/deviceauth" "data-terminal-cloud/internal/devicebinding" "data-terminal-cloud/internal/hardwarelicense" "data-terminal-cloud/internal/hardwareprovision" "data-terminal-cloud/internal/orchestration" ) type HardwareAgentSessionService interface { CreateHardwareAgent(context.Context, deviceauth.Identity, string, string, []byte, orchestration.HardwareAgentCreateInput) (orchestration.HardwareAgentCreateResponse, bool, error) ProcessHardwareAgentEvent(context.Context, deviceauth.Identity, string, []byte, orchestration.HardwareAgentEventInput) (orchestration.HardwareAgentEventResponse, error) } type HardwareAgentProvisioningService interface { Provision(context.Context, deviceauth.Identity, string, hardwareprovision.ProvisionInput) (hardwareprovision.ProvisionResponse, error) } type HardwareAgentLicenseService interface { Acknowledge(context.Context, deviceauth.Identity, string, hardwarelicense.AcknowledgeInput) error } func AcknowledgeHardwareAgentLicenseHandler(service HardwareAgentLicenseService) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { identity, ok := deviceauth.FromContext(r.Context()) if !ok { WriteError(w, r, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败") return } var body struct { LicenseRef string `json:"license_ref"` Digest string `json:"digest"` Result string `json:"result"` ErrorCode string `json:"error_code"` } if !decodeWebJSON(w, r, &body, "License确认参数不正确") { return } err := service.Acknowledge(r.Context(), identity, RequestID(r.Context()), hardwarelicense.AcknowledgeInput{LicenseRef: body.LicenseRef, Digest: body.Digest, Result: body.Result, ErrorCode: body.ErrorCode}) if err != nil { switch { case errors.Is(err, hardwarelicense.ErrAssignment), errors.Is(err, hardwarelicense.ErrAcknowledgement): WriteError(w, r, http.StatusConflict, "HARDWARE_LICENSE_ACK_CONFLICT", "License不属于当前设备或摘要不一致") case errors.Is(err, hardwarelicense.ErrExpired): WriteError(w, r, http.StatusConflict, "HARDWARE_LICENSE_EXPIRED", "License已经到期") default: WriteAPIError(w, r, http.StatusServiceUnavailable, Error{Code: "HARDWARE_LICENSE_ACK_FAILED", Message: "License确认暂时失败", Retryable: true}) } return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"license_ref": body.LicenseRef, "status": strings.ToUpper(strings.TrimSpace(body.Result))}) }) } func ProvisionHardwareAgentCredentialHandler(service HardwareAgentProvisioningService) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Cache-Control", "no-store") writer.Header().Set("Pragma", "no-cache") identity, ok := deviceauth.FromContext(request.Context()) if !ok { WriteError(writer, request, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败") return } key := strings.TrimSpace(request.Header.Get("Idempotency-Key")) if len(key) < 16 || len(key) > 128 { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "缺少有效的重复请求识别码") return } decoder := json.NewDecoder(request.Body) decoder.DisallowUnknownFields() var input hardwareprovision.ProvisionInput if err := decoder.Decode(&input); err != nil || ensureJSONEOF(decoder) != nil { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "火山设备注册参数不正确") return } response, err := service.Provision(request.Context(), identity, RequestID(request.Context()), input) if err != nil { switch { case errors.Is(err, hardwareprovision.ErrUnavailable): WriteAPIError(writer, request, http.StatusServiceUnavailable, Error{Code: "HARDWARE_AGENT_PROVISIONING_UNAVAILABLE", Message: "平台尚未配置火山设备注册服务", Retryable: false}) case errors.Is(err, devicebinding.ErrConfirmationRequired): WriteError(writer, request, http.StatusConflict, "ONBOARDING_ACTIVATION_REQUIRED", "请通过首次接入激活任务获取运行配置") case errors.Is(err, hardwareprovision.ErrInvalid): WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "火山硬件标识不正确") case errors.Is(err, hardwareprovision.ErrBindingMissing): WriteError(writer, request, http.StatusConflict, "HARDWARE_AGENT_BINDING_REQUIRED", "设备尚未绑定火山硬件智能体") case errors.Is(err, hardwareprovision.ErrBindingMismatch): WriteError(writer, request, http.StatusConflict, "HARDWARE_AGENT_PRODUCT_MISMATCH", "设备绑定产品与平台注册产品不一致") case errors.Is(err, hardwareprovision.ErrHardwareMismatch): WriteError(writer, request, http.StatusConflict, "HARDWARE_AGENT_HARDWARE_MISMATCH", "硬件标识与已注册凭据不一致") case errors.Is(err, hardwareprovision.ErrActivationState): WriteError(writer, request, http.StatusConflict, "DEVICE_ACTIVATION_REQUIRED", "设备必须先完成用户绑定,才能申请火山运行凭据") default: WriteAPIError(writer, request, http.StatusServiceUnavailable, Error{Code: "HARDWARE_AGENT_PROVISIONING_FAILED", Message: "火山设备注册暂时失败", Retryable: true}) } return } WriteJSON(writer, request, http.StatusOK, response) }) } func CreateHardwareAgentSessionHandler(service HardwareAgentSessionService) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { identity, ok := deviceauth.FromContext(request.Context()) if !ok { WriteError(writer, request, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败") return } key := strings.TrimSpace(request.Header.Get("Idempotency-Key")) if len(key) < 16 || len(key) > 128 { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "缺少有效的重复请求识别码") return } rawBody, err := io.ReadAll(request.Body) if err != nil { WriteError(writer, request, http.StatusBadRequest, "INVALID_REQUEST_BODY", "请求正文无法读取") return } decoder := json.NewDecoder(bytes.NewReader(rawBody)) decoder.DisallowUnknownFields() var input orchestration.HardwareAgentCreateInput if err = decoder.Decode(&input); err != nil || ensureJSONEOF(decoder) != nil { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "硬件智能体会话参数不正确") return } if input.DeviceTime.IsZero() || input.ConfigRevision < 1 || !input.Capabilities.HardwareAgent || !input.Capabilities.TranscriptUpload || !input.Capabilities.DynamicSessionConfig || !input.Capabilities.CredentialRotation || strings.TrimSpace(input.Capabilities.SDKVersion) == "" || len(input.Capabilities.SDKVersion) > 80 || strings.TrimSpace(input.Capabilities.InputAudioFormat) == "" || len(input.Capabilities.InputAudioFormat) > 80 || strings.TrimSpace(input.Capabilities.OutputAudioFormat) == "" || len(input.Capabilities.OutputAudioFormat) > 80 || input.RequestedGoal != "open_conversation" { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "设备时间、配置版本或硬件智能体能力不正确") return } validTransport := false for _, transport := range input.Capabilities.Transports { if transport != "high_quality" && transport != "low_load" { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "硬件智能体传输模式不受支持") return } validTransport = true } if !validTransport { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "设备必须声明至少一种支持的硬件智能体传输模式") return } response, replayed, err := service.CreateHardwareAgent(request.Context(), identity, key, RequestID(request.Context()), rawBody, input) if replayed { writer.Header().Set("Idempotency-Replayed", "true") } if err != nil { writeCreateConversationError(writer, request, err) return } WriteJSON(writer, request, http.StatusCreated, response) }) } func HardwareAgentEventHandler(service HardwareAgentSessionService) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { identity, ok := deviceauth.FromContext(request.Context()) if !ok { WriteError(writer, request, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败") return } rawBody, err := io.ReadAll(request.Body) if err != nil { WriteError(writer, request, http.StatusBadRequest, "INVALID_REQUEST_BODY", "请求正文无法读取") return } decoder := json.NewDecoder(bytes.NewReader(rawBody)) decoder.DisallowUnknownFields() var input orchestration.HardwareAgentEventInput if err = decoder.Decode(&input); err != nil || ensureJSONEOF(decoder) != nil || strings.TrimSpace(input.EventID) == "" || input.SequenceNo < 1 || input.OccurredAt.IsZero() || input.OccurredAt.After(time.Now().UTC().Add(5*time.Minute)) || strings.TrimSpace(input.ProviderSessionRef) == "" || strings.TrimSpace(input.SessionNonce) == "" { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "硬件智能体事件参数不正确") return } switch input.EventType { case "session_started", "session_ended", "session_failed", "session_updated", "first_audio_received": default: WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "不支持的硬件智能体事件类型") return } if input.EventType == "session_started" && (!input.DynamicConfigApplied || strings.TrimSpace(input.ProviderConnectionRef) == "" || strings.TrimSpace(input.BotID) == "" || strings.TrimSpace(input.Transport) == "" || strings.TrimSpace(input.ConfigDigest) == "") { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "会话启动前必须证明动态人设配置已生效") return } response, err := service.ProcessHardwareAgentEvent(request.Context(), identity, request.PathValue("id"), rawBody, input) if err != nil { switch { case errors.Is(err, orchestration.ErrConversationNotFound): WriteError(writer, request, http.StatusNotFound, "CONVERSATION_NOT_FOUND", "通话不存在或不属于当前设备") case errors.Is(err, orchestration.ErrHardwareSessionProof): WriteError(writer, request, http.StatusUnauthorized, "HARDWARE_SESSION_PROOF_INVALID", "硬件智能体会话凭证无效") case errors.Is(err, orchestration.ErrHardwareEventConflict), errors.Is(err, orchestration.ErrConversationStateConflict), errors.Is(err, orchestration.ErrOnboardingState): WriteError(writer, request, http.StatusConflict, "HARDWARE_AGENT_EVENT_CONFLICT", "硬件智能体事件与当前会话状态冲突") default: WriteAPIError(writer, request, http.StatusServiceUnavailable, Error{Code: "HARDWARE_AGENT_EVENT_UNAVAILABLE", Message: "暂时无法处理硬件智能体事件", Retryable: true}) } return } WriteJSON(writer, request, http.StatusOK, response) }) }