@@ -0,0 +1,216 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"data-terminal-cloud/internal/deviceauth"
|
||||
"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, 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":
|
||||
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):
|
||||
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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user