Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions backend/modules/socai/handler/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,54 @@ package handler
import (
"context"
"encoding/json"
"errors"
"io"
"net"
"net/http"

"github.com/gin-gonic/gin"
"github.com/threatwinds/go-sdk/catcher"
)

// classifyClientErr maps transport-level failures to a user-safe (httpStatus, message).
// Never echoes err.Error() — it can carry URLs, header values, or plugin internals.
func classifyClientErr(err error) (int, string) {
switch {
case errors.Is(err, context.DeadlineExceeded):
return http.StatusGatewayTimeout, "SOC AI agent timed out"
case errors.Is(err, context.Canceled):
return 499, "request canceled"
}
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
return http.StatusGatewayTimeout, "SOC AI agent timed out"
}
return http.StatusBadGateway, "SOC AI agent unreachable"
}

// messageForStatus maps an upstream HTTP status to a fixed user-safe message,
// so upstream error bodies (which may contain the system prompt) never reach the client.
func messageForStatus(status int) string {
switch status {
case http.StatusUnauthorized, http.StatusForbidden:
return "SOC AI agent rejected credentials"
case http.StatusRequestTimeout, http.StatusGatewayTimeout:
return "SOC AI agent timed out"
case http.StatusTooManyRequests:
return "SOC AI agent rate limited"
case http.StatusNotFound:
return "SOC AI endpoint not found"
case http.StatusBadGateway, http.StatusServiceUnavailable:
return "SOC AI agent unreachable"
case http.StatusBadRequest, http.StatusUnprocessableEntity:
return "SOC AI agent rejected the request"
}
if status >= 500 {
return "SOC AI agent internal error"
}
return "SOC AI agent error"
}

type socAIStreamer interface {
StreamAgentTask(ctx context.Context, body []byte) (*http.Response, error)
}
Expand Down Expand Up @@ -65,14 +106,21 @@ func (h *ChatHandler) Chat(c *gin.Context) {
resp, err := h.client.StreamAgentTask(c.Request.Context(), body)
if err != nil {
_ = catcher.Error("SocAIChat: stream request failed", err, nil)
c.JSON(http.StatusBadGateway, gin.H{"status": "error", "message": "SOC AI agent unreachable"})
status, msg := classifyClientErr(err)
c.JSON(status, gin.H{"status": "error", "message": msg})
return
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
msg, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
c.JSON(http.StatusBadGateway, gin.H{"status": "error", "message": string(msg)})
// Upstream error body may include the loaded plugin config (system prompt,
// masked-but-still-sensitive fields). Log for ops, return a status-derived message.
upstream, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
_ = catcher.Error("SocAIChat: upstream error", nil, map[string]any{
"status": resp.StatusCode,
"body": string(upstream),
})
c.JSON(http.StatusBadGateway, gin.H{"status": "error", "message": messageForStatus(resp.StatusCode)})
return
}

Expand Down
7 changes: 5 additions & 2 deletions backend/modules/socai/handler/socai.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,15 @@ func (h *SocAIHandler) Analyze(c *gin.Context) {

statusCode, _, err := h.client.Analyze(c.Request.Context(), bodyBytes)
if err != nil {
writeError(c, fmt.Sprintf("%s: %s", ctx, err.Error()))
_ = catcher.Error(ctx+": analyze request failed", err, nil)
status, msg := classifyClientErr(err)
c.JSON(status, gin.H{"status": "error", "message": msg})
return
}

if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
writeError(c, fmt.Sprintf("%s: unexpected response from SOC AI service: %d", ctx, statusCode))
_ = catcher.Error(ctx+": unexpected upstream status", nil, map[string]any{"status": statusCode})
c.JSON(http.StatusBadGateway, gin.H{"status": "error", "message": messageForStatus(statusCode)})
return
}

Expand Down
Loading