|
- package relay
-
- import (
- "crypto/sha256"
- "encoding/hex"
- "strings"
-
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/model"
- )
-
- const maxUpstreamRequestSnapshotBytes = 256 * 1024
-
- func buildUpstreamRequestSnapshot(raw []byte) *model.TaskUpstreamRequestSnapshot {
- if len(raw) == 0 {
- return nil
- }
- snapshot := &model.TaskUpstreamRequestSnapshot{
- RawBytes: len(raw),
- SHA256: sha256Hex(raw),
- }
-
- var body any
- if err := common.Unmarshal(raw, &body); err == nil {
- sanitized, redacted := sanitizeUpstreamRequestValue(body)
- snapshot.Body = sanitized
- snapshot.Redacted = redacted
- } else {
- snapshot.Body = map[string]any{
- "raw_preview": truncateStringToBytes(string(raw), maxUpstreamRequestSnapshotBytes/2),
- "format": "non_json",
- }
- snapshot.Truncated = len(raw) > maxUpstreamRequestSnapshotBytes/2
- }
-
- finalizeUpstreamRequestSnapshot(snapshot, raw)
- return snapshot
- }
-
- func BuildUpstreamRequestSnapshotForTask(raw []byte) *model.TaskUpstreamRequestSnapshot {
- return buildUpstreamRequestSnapshot(raw)
- }
-
- func sanitizeUpstreamRequestValue(value any) (any, bool) {
- switch v := value.(type) {
- case map[string]any:
- out := make(map[string]any, len(v))
- redacted := false
- for key, item := range v {
- sanitized, itemRedacted := sanitizeUpstreamRequestValue(item)
- out[key] = sanitized
- redacted = redacted || itemRedacted
- }
- return out, redacted
- case []any:
- out := make([]any, len(v))
- redacted := false
- for i, item := range v {
- sanitized, itemRedacted := sanitizeUpstreamRequestValue(item)
- out[i] = sanitized
- redacted = redacted || itemRedacted
- }
- return out, redacted
- case string:
- if mediaType, ok := parseBase64DataURI(v); ok {
- return map[string]any{
- "redacted": true,
- "kind": "data_uri",
- "media_type": mediaType,
- "bytes": len(v),
- "sha256": sha256Hex([]byte(v)),
- }, true
- }
- return v, false
- default:
- return value, false
- }
- }
-
- func parseBase64DataURI(value string) (string, bool) {
- lower := strings.ToLower(value)
- if !strings.HasPrefix(lower, "data:") {
- return "", false
- }
- marker := ";base64,"
- idx := strings.Index(lower, marker)
- if idx <= len("data:") {
- return "", false
- }
- mediaType := value[len("data:"):idx]
- if !strings.HasPrefix(strings.ToLower(mediaType), "image/") &&
- !strings.HasPrefix(strings.ToLower(mediaType), "audio/") &&
- !strings.HasPrefix(strings.ToLower(mediaType), "video/") {
- return "", false
- }
- return mediaType, true
- }
-
- func finalizeUpstreamRequestSnapshot(snapshot *model.TaskUpstreamRequestSnapshot, raw []byte) {
- updateStoredBytes(snapshot)
- if snapshot.StoredBytes <= maxUpstreamRequestSnapshotBytes {
- return
- }
-
- bodyBytes, err := common.Marshal(snapshot.Body)
- previewSource := raw
- if err == nil {
- previewSource = bodyBytes
- }
- snapshot.Body = map[string]any{
- "truncated_preview": truncateStringToBytes(string(previewSource), maxUpstreamRequestSnapshotBytes/2),
- }
- snapshot.Truncated = true
- updateStoredBytes(snapshot)
- for snapshot.StoredBytes > maxUpstreamRequestSnapshotBytes {
- body, _ := snapshot.Body.(map[string]any)
- preview, _ := body["truncated_preview"].(string)
- if len(preview) == 0 {
- break
- }
- body["truncated_preview"] = truncateStringToBytes(preview, len(preview)/2)
- snapshot.Body = body
- updateStoredBytes(snapshot)
- }
- }
-
- func updateStoredBytes(snapshot *model.TaskUpstreamRequestSnapshot) {
- for i := 0; i < 4; i++ {
- data, err := common.Marshal(snapshot)
- if err != nil {
- return
- }
- if snapshot.StoredBytes == len(data) {
- return
- }
- snapshot.StoredBytes = len(data)
- }
- }
-
- func truncateStringToBytes(value string, maxBytes int) string {
- if maxBytes <= 0 || len(value) <= maxBytes {
- return value
- }
- return value[:maxBytes]
- }
-
- func sha256Hex(data []byte) string {
- sum := sha256.Sum256(data)
- return hex.EncodeToString(sum[:])
- }
|