diff --git a/backend/internal/handler/grok_audio.go b/backend/internal/handler/grok_audio.go index 08a8ff3c6..c95b5ce55 100644 --- a/backend/internal/handler/grok_audio.go +++ b/backend/internal/handler/grok_audio.go @@ -98,6 +98,12 @@ func (h *OpenAIGatewayHandler) GrokRealtime(c *gin.Context) { cancelProbe() if openErr != nil { reqLog.Warn("grok_realtime.pre_accept_failed", zap.Int64("account_id", account.ID), zap.Error(openErr)) + statusCode := http.StatusBadGateway + var dialErr *service.GrokRealtimeDialError + if errors.As(openErr, &dialErr) && dialErr.StatusCode > 0 { + statusCode = dialErr.StatusCode + } + h.gatewayService.HandleGrokRealtimeUpstreamError(c.Request.Context(), account, statusCode, []byte(openErr.Error())) release() release = nil failed[account.ID] = struct{}{} diff --git a/backend/internal/service/grok_audio.go b/backend/internal/service/grok_audio.go index a2fb33f12..771cae09f 100644 --- a/backend/internal/service/grok_audio.go +++ b/backend/internal/service/grok_audio.go @@ -139,6 +139,16 @@ func (s *OpenAIGatewayService) ProxyGrokRealtime(ctx context.Context, c *gin.Con type GrokRealtimeUpstream struct{ conn openAIWSClientConn } +// GrokRealtimeDialError preserves an HTTP status returned before WebSocket +// upgrade so handlers can apply the normal Grok account policy. +type GrokRealtimeDialError struct { + StatusCode int + Err error +} + +func (e *GrokRealtimeDialError) Error() string { return e.Err.Error() } +func (e *GrokRealtimeDialError) Unwrap() error { return e.Err } + func (u *GrokRealtimeUpstream) Close() error { if u == nil || u.conn == nil { return nil @@ -171,13 +181,22 @@ func (s *OpenAIGatewayService) OpenGrokRealtime(ctx context.Context, account *Ac if account.ProxyID != nil && account.Proxy != nil { proxyURL = account.Proxy.URL() } - conn, _, _, err := s.getOpenAIWSPassthroughDialer().Dial(ctx, u.String(), headers, proxyURL) + conn, status, _, err := s.getOpenAIWSPassthroughDialer().Dial(ctx, u.String(), headers, proxyURL) if err != nil { - return nil, err + return nil, &GrokRealtimeDialError{StatusCode: status, Err: err} } return &GrokRealtimeUpstream{conn: conn}, nil } +// HandleGrokRealtimeUpstreamError applies the shared Grok account policy to a +// failed pre-accept WebSocket handshake. +func (s *OpenAIGatewayService) HandleGrokRealtimeUpstreamError(ctx context.Context, account *Account, statusCode int, body []byte) { + if statusCode <= 0 { + statusCode = http.StatusBadGateway + } + s.handleGrokAccountUpstreamError(ctx, account, statusCode, nil, body) +} + func (s *OpenAIGatewayService) ProxyGrokRealtimeConn(ctx context.Context, c *gin.Context, client *coderws.Conn, upstream *GrokRealtimeUpstream) (bool, error) { if s == nil || client == nil || upstream == nil || upstream.conn == nil { return false, fmt.Errorf("realtime connection is required")