KaThaNg commited on
Commit
b2ce9e7
·
verified ·
1 Parent(s): e720b3d

Update handlers.go

Browse files
Files changed (1) hide show
  1. handlers.go +76 -64
handlers.go CHANGED
@@ -9,6 +9,7 @@ import (
9
  "net"
10
  "net/http"
11
  "net/url"
 
12
  "strings"
13
  "time"
14
 
@@ -16,42 +17,52 @@ import (
16
  "github.com/google/uuid"
17
  )
18
 
 
 
 
 
 
 
 
 
 
19
  func maskURL(rawURL string) string {
20
  u, err := url.Parse(rawURL)
21
  if err != nil {
22
- return rawURL
23
  }
24
 
25
  originalHostname := u.Hostname()
26
  if originalHostname == "" {
27
- return rawURL
28
  }
29
 
 
30
  if net.ParseIP(originalHostname) != nil {
31
  return rawURL
32
  }
33
 
34
  hostParts := strings.Split(originalHostname, ".")
35
  maskedHostname := ""
36
- prefixToKeepLength := 3
37
 
38
- if len(hostParts) == 1 {
39
  nameOnly := hostParts[0]
40
  prefix := nameOnly
41
  if len(nameOnly) > prefixToKeepLength {
42
  prefix = nameOnly[:prefixToKeepLength]
43
  }
44
  maskedHostname = prefix + "******"
45
- } else if len(hostParts) >= 2 {
46
- tld := hostParts[len(hostParts)-1]
47
- namePart := strings.Join(hostParts[:len(hostParts)-1], ".")
48
 
49
  prefix := namePart
50
  if len(namePart) > prefixToKeepLength {
51
  prefix = namePart[:prefixToKeepLength]
52
  }
53
  maskedHostname = prefix + "******" + "." + tld
54
- } else {
55
  maskedHostname = "******"
56
  }
57
 
@@ -60,33 +71,35 @@ func maskURL(rawURL string) string {
60
  newHost += ":" + u.Port()
61
  }
62
 
63
- maskedU := *u
64
  maskedU.Host = newHost
65
 
66
  return maskedU.String()
67
  }
68
 
 
69
  func HealthCheckHandler(c *gin.Context) {
70
  c.JSON(http.StatusOK, gin.H{"status": "healthy"})
71
  }
72
 
 
 
 
73
  func MessagesHandler(c *gin.Context) {
74
- requestID := fmt.Sprintf("msg_%s", uuid.NewString()[:24])
75
- cfg := LoadConfig()
76
  startTime := time.Now()
77
 
78
  var claudeReq ClaudeRequest
79
  bodyBytes, err := io.ReadAll(c.Request.Body)
80
  if err != nil {
81
- // Giữ lại: Log lỗi quan trọng
82
  log.Printf("ERROR: [%s] Không thể đọc nội dung yêu cầu: %v", requestID, err)
83
  sendClaudeError(c, http.StatusBadRequest, "invalid_request_error", "Không thể đọc nội dung yêu cầu.")
84
  return
85
  }
86
- c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // Restore request body
87
 
88
  if err := json.Unmarshal(bodyBytes, &claudeReq); err != nil {
89
- // Giữ lại: Log lỗi quan trọng
90
  log.Printf("ERROR: [%s] Không thể giải mã JSON yêu cầu: %v. Body: %s", requestID, err, string(bodyBytes))
91
  sendClaudeError(c, http.StatusBadRequest, "invalid_request_error", "Định dạng JSON không hợp lệ trong nội dung yêu cầu.")
92
  return
@@ -95,80 +108,89 @@ func MessagesHandler(c *gin.Context) {
95
  isStreaming := claudeReq.Stream
96
  modelRequested := claudeReq.Model
97
  if modelRequested == "" {
98
- modelRequested = "unknown_model"
99
  }
100
- // Giữ lại: Log INFO quan trọng về yêu cầu nhận được
101
  log.Printf("INFO: [%s] Nhận yêu cầu. Stream: %t, Model: %s, ClientIP: %s, NativeClaude: %t",
102
  requestID, isStreaming, modelRequested, c.ClientIP(), cfg.UpstreamNativeClaudeFormat)
103
 
104
  var upstreamURL string
105
  var upstreamRequestBodyBytes []byte
106
- var openAIReqForLogging *OpenAIRequest // Only used if not native claude, for logging model
107
 
108
  if cfg.UpstreamNativeClaudeFormat {
109
- // Upstream expects Claude format
110
- upstreamURL = strings.TrimRight(cfg.OpenAIAPIEndpoint, "/") + "/v1/messages" // Standard Claude endpoint path
111
- upstreamRequestBodyBytes = bodyBytes // Use original Claude request body
112
- // Giữ lại: Log INFO
113
  log.Printf("INFO: [%s] Upstream là Claude native. Chuyển tiếp yêu cầu gốc đến %s", requestID, maskURL(upstreamURL))
114
  } else {
115
- // Upstream expects OpenAI format, convert
116
- upstreamURL = strings.TrimRight(cfg.OpenAIAPIEndpoint, "/") + "/v1/chat/completions" // Standard OpenAI endpoint path
117
  openAIReq, errConv := convertClaudeRequestToOpenAI(&claudeReq)
118
  if errConv != nil {
119
- // Giữ lại: Log lỗi quan trọng
120
  log.Printf("ERROR: [%s] Không thể chuyển đổi yêu cầu Claude sang OpenAI: %v", requestID, errConv)
121
  sendClaudeError(c, http.StatusBadRequest, "invalid_request_error", fmt.Sprintf("Lỗi chuyển đổi dữ liệu yêu cầu: %v", errConv))
122
  return
123
  }
124
- openAIReq.Stream = isStreaming // Ensure stream flag is set correctly
125
- openAIReqForLogging = openAIReq // Store for logging model used in SSE
126
 
127
  openaiReqBytesMarshal, errMarshal := json.Marshal(openAIReq)
128
  if errMarshal != nil {
129
- // Giữ lại: Log lỗi quan trọng
130
  log.Printf("ERROR: [%s] Không thể marshal yêu cầu OpenAI JSON: %v", requestID, errMarshal)
131
  sendClaudeError(c, http.StatusInternalServerError, "internal_server_error", "Không thể chuẩn bị yêu cầu upstream.")
132
  return
133
  }
134
  upstreamRequestBodyBytes = openaiReqBytesMarshal
135
- // Giữ lại: Log INFO
136
  log.Printf("INFO: [%s] Upstream là OpenAI. Chuyển đổi và gửi yêu cầu đến %s", requestID, maskURL(upstreamURL))
137
  }
138
 
 
139
  req, err := http.NewRequestWithContext(c.Request.Context(), "POST", upstreamURL, bytes.NewBuffer(upstreamRequestBodyBytes))
140
  if err != nil {
141
- // Giữ lại: Log lỗi quan trọng
142
  log.Printf("ERROR: [%s] Không thể tạo yêu cầu HTTP upstream: %v", requestID, err)
143
  sendClaudeError(c, http.StatusInternalServerError, "internal_server_error", "Không thể tạo yêu cầu upstream.")
144
  return
145
  }
146
 
 
147
  req.Header.Set("Content-Type", "application/json")
148
  if isStreaming {
149
  req.Header.Set("Accept", "text/event-stream")
150
  } else {
151
  req.Header.Set("Accept", "application/json")
152
  }
153
- if cfg.OpenAIAPIKey != "" {
154
- req.Header.Set("Authorization", "Bearer "+cfg.OpenAIAPIKey)
155
- }
156
- if cfg.UpstreamNativeClaudeFormat {
157
- // Add Anthropic-Version header if upstream is native Claude and it's required
158
- // Example: req.Header.Set("anthropic-version", "2023-06-01")
159
- // For now, this is commented out as it depends on specific upstream requirements.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  }
161
 
162
- // Giữ lại: Log INFO quan trọng về việc gửi yêu cầu upstream
163
  log.Printf("INFO: [%s] Gửi yêu cầu upstream (Stream=%t) đến %s...", requestID, isStreaming, maskURL(upstreamURL))
164
 
 
165
  httpClient := &http.Client{
166
- Transport: cfg.UpstreamTransport,
167
- Timeout: 0, // Timeout is handled by Transport's DialContext and ResponseHeaderTimeout
168
  }
169
  upstreamResp, err := httpClient.Do(req)
170
  if err != nil {
171
- // Giữ lại: Log lỗi quan trọng
172
  log.Printf("ERROR: [%s] Yêu cầu upstream thất bại: %v", requestID, err)
173
  if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
174
  sendClaudeError(c, http.StatusGatewayTimeout, "api_error", fmt.Sprintf("Gateway Timeout khi kết nối upstream (%v).", cfg.ConnectTimeout))
@@ -177,19 +199,17 @@ func MessagesHandler(c *gin.Context) {
177
  }
178
  return
179
  }
180
- // Giữ lại: Log INFO quan trọng về trạng thái upstream
181
  log.Printf("INFO: [%s] Nhận trạng thái upstream: %d (%s)", requestID, upstreamResp.StatusCode, http.StatusText(upstreamResp.StatusCode))
182
 
 
183
  if upstreamResp.StatusCode != http.StatusOK {
184
  errorBodyBytes, readErr := io.ReadAll(upstreamResp.Body)
185
  if readErr != nil {
186
- // Giữ lại: Log cảnh báo
187
  log.Printf("WARN: [%s] Không thể đọc nội dung lỗi upstream (Status %d): %v", requestID, upstreamResp.StatusCode, readErr)
188
  }
189
- _ = upstreamResp.Body.Close()
190
 
191
  errorBodyStr := string(errorBodyBytes)
192
- // Giữ lại: Log lỗi quan trọng
193
  log.Printf("ERROR: [%s] Upstream trả về lỗi %d. Chi tiết ngắn: %s", requestID, upstreamResp.StatusCode, truncateString(strings.TrimSpace(errorBodyStr), 100))
194
 
195
  var errorType string
@@ -210,54 +230,47 @@ func MessagesHandler(c *gin.Context) {
210
  return
211
  }
212
 
 
213
  if isStreaming {
214
  if cfg.UpstreamNativeClaudeFormat {
215
- // Giữ lại: Log INFO quan trọng
216
  log.Printf("INFO: [%s] Upstream Claude native stream nhận được. Bắt đầu proxy SSE (với hack usage).", requestID)
217
- proxyClaudeNativeSSE(c, upstreamResp, requestID, &claudeReq) // SỬA Ở ĐÂY: Thêm &claudeReq
218
  } else {
219
- // Giữ lại: Log INFO quan trọng
220
  log.Printf("INFO: [%s] Upstream OpenAI stream nhận được. Bắt đầu chuyển đổi SSE.", requestID)
221
- modelForConversion := claudeReq.Model
222
  if openAIReqForLogging != nil && openAIReqForLogging.Model != "" {
223
- modelForConversion = openAIReqForLogging.Model
224
  }
225
  streamOpenAIResponseToClaudeSSE(c, upstreamResp, requestID, modelForConversion, &claudeReq)
226
  }
227
  } else { // Non-streaming
228
- // Giữ lại: Log INFO quan trọng
229
  log.Printf("INFO: [%s] Phản hồi non-stream upstream nhận được. Đang xử lý.", requestID)
230
- defer upstreamResp.Body.Close()
231
 
232
  respBodyBytes, errRead := io.ReadAll(upstreamResp.Body)
233
  if errRead != nil {
234
- // Giữ lại: Log lỗi quan trọng
235
  log.Printf("ERROR: [%s] Không thể đọc nội dung phản hồi non-streaming upstream: %v", requestID, errRead)
236
  sendClaudeError(c, http.StatusBadGateway, "api_error", "Không thể đọc phản hồi upstream.")
237
  return
238
  }
239
 
240
  if cfg.UpstreamNativeClaudeFormat {
241
- // Giữ lại: Log INFO
242
  log.Printf("INFO: [%s] Upstream Claude native non-stream response nhận được. Chuyển tiếp trực tiếp.", requestID)
243
- var tempClaudeResp map[string]interface{}
244
  if errJson := json.Unmarshal(respBodyBytes, &tempClaudeResp); errJson != nil {
245
- // Giữ lại: Log lỗi quan trọng
246
  log.Printf("ERROR: [%s] Upstream Claude native response không phải JSON hợp lệ: %v. Body: %s", requestID, errJson, string(respBodyBytes))
247
  sendClaudeError(c, http.StatusBadGateway, "api_error", "API upstream (Claude native) trả về JSON không hợp lệ.")
248
  return
249
  }
250
  clientContentType := upstreamResp.Header.Get("Content-Type")
251
  if clientContentType == "" {
252
- clientContentType = "application/json; charset=utf-8"
253
  }
254
- c.Data(http.StatusOK, clientContentType, respBodyBytes)
255
  } else {
256
- // Giữ lại: Log INFO
257
  log.Printf("INFO: [%s] Phản hồi non-stream OpenAI upstream nhận được. Đang chuyển đổi.", requestID)
258
  var openAIResp OpenAIResponse
259
  if errJson := json.Unmarshal(respBodyBytes, &openAIResp); errJson != nil {
260
- // Giữ lại: Log lỗi quan trọng
261
  log.Printf("ERROR: [%s] Không thể giải mã JSON non-streaming OpenAI upstream: %v. Body: %s", requestID, errJson, string(respBodyBytes))
262
  sendClaudeError(c, http.StatusBadGateway, "api_error", "API upstream (OpenAI) trả về JSON không hợp lệ.")
263
  return
@@ -265,18 +278,17 @@ func MessagesHandler(c *gin.Context) {
265
 
266
  claudeRespToClient, errConv := convertOpenAIResponseToClaude(&openAIResp, requestID)
267
  if errConv != nil {
268
- // Giữ lại: Log lỗi quan trọng
269
  log.Printf("ERROR: [%s] Không thể chuyển đổi phản hồi non-streaming OpenAI: %v", requestID, errConv)
270
  sendClaudeError(c, http.StatusInternalServerError, "internal_server_error", fmt.Sprintf("Lỗi xử lý phản hồi upstream: %v", errConv))
271
  return
272
  }
273
  c.JSON(http.StatusOK, claudeRespToClient)
274
  }
275
- // Giữ lại: Log INFO hữu ích về thời gian xử lý
276
  log.Printf("INFO: [%s] Xử lý thành công yêu cầu non-streaming trong %v", requestID, time.Since(startTime))
277
  }
278
  }
279
 
 
280
  func sendClaudeError(c *gin.Context, statusCode int, errorType string, message string) {
281
  errResp := ClaudeErrorResponse{
282
  Type: "error",
@@ -285,14 +297,14 @@ func sendClaudeError(c *gin.Context, statusCode int, errorType string, message s
285
  Message: message,
286
  },
287
  }
288
- if statusCode < 400 || statusCode > 599 {
289
- // Giữ lại: Log cảnh báo về mã trạng thái không hợp lệ
290
  log.Printf("WARN: Mã trạng thái không hợp lệ %d được cung cấp cho lỗi, mặc định thành 500.", statusCode)
291
  statusCode = http.StatusInternalServerError
292
  }
293
- c.AbortWithStatusJSON(statusCode, errResp)
294
  }
295
 
 
296
  func truncateString(s string, maxLength int) string {
297
  if len(s) <= maxLength {
298
  return s
 
9
  "net"
10
  "net/http"
11
  "net/url"
12
+ "os" // Thêm os để sử dụng os.Getenv
13
  "strings"
14
  "time"
15
 
 
17
  "github.com/google/uuid"
18
  )
19
 
20
+ const (
21
+ // ANTHROPIC_VERSION_ENV_VAR là tên biến môi trường để lấy phiên bản Anthropic
22
+ ANTHROPIC_VERSION_ENV_VAR = "ANTHROPIC_VERSION"
23
+ // DEFAULT_ANTHROPIC_VERSION là phiên bản Anthropic mặc định nếu không được đặt trong biến môi trường
24
+ DEFAULT_ANTHROPIC_VERSION = "2023-06-01"
25
+ )
26
+
27
+ // maskURL che giấu hostname trong URL để bảo mật log.
28
+ // Ví dụ: "https://api.example.com/v1" -> "https://api******.com/v1"
29
  func maskURL(rawURL string) string {
30
  u, err := url.Parse(rawURL)
31
  if err != nil {
32
+ return rawURL // Trả về URL gốc nếu không parse được
33
  }
34
 
35
  originalHostname := u.Hostname()
36
  if originalHostname == "" {
37
+ return rawURL // Không có hostname để che
38
  }
39
 
40
+ // Không che giấu địa chỉ IP
41
  if net.ParseIP(originalHostname) != nil {
42
  return rawURL
43
  }
44
 
45
  hostParts := strings.Split(originalHostname, ".")
46
  maskedHostname := ""
47
+ prefixToKeepLength := 3 // Số ký tự đầu tiên của phần tên miền chính giữ lại
48
 
49
+ if len(hostParts) == 1 { // ví dụ: "localhost"
50
  nameOnly := hostParts[0]
51
  prefix := nameOnly
52
  if len(nameOnly) > prefixToKeepLength {
53
  prefix = nameOnly[:prefixToKeepLength]
54
  }
55
  maskedHostname = prefix + "******"
56
+ } else if len(hostParts) >= 2 { // ví dụ: "api.example.com"
57
+ tld := hostParts[len(hostParts)-1] // "com"
58
+ namePart := strings.Join(hostParts[:len(hostParts)-1], ".") // "api.example"
59
 
60
  prefix := namePart
61
  if len(namePart) > prefixToKeepLength {
62
  prefix = namePart[:prefixToKeepLength]
63
  }
64
  maskedHostname = prefix + "******" + "." + tld
65
+ } else { // Trường hợp không mong muốn, che toàn bộ
66
  maskedHostname = "******"
67
  }
68
 
 
71
  newHost += ":" + u.Port()
72
  }
73
 
74
+ maskedU := *u // Tạo bản sao của URL
75
  maskedU.Host = newHost
76
 
77
  return maskedU.String()
78
  }
79
 
80
+ // HealthCheckHandler xử lý kiểm tra tình trạng của proxy.
81
  func HealthCheckHandler(c *gin.Context) {
82
  c.JSON(http.StatusOK, gin.H{"status": "healthy"})
83
  }
84
 
85
+ // MessagesHandler xử lý các yêu cầu đến /v1/messages.
86
+ // Nó chuyển đổi yêu cầu từ định dạng Claude sang OpenAI (nếu cần),
87
+ // gửi đến upstream API, và chuyển đổi phản hồi trở lại định dạng Claude.
88
  func MessagesHandler(c *gin.Context) {
89
+ requestID := fmt.Sprintf("msg_%s", uuid.NewString()[:24]) // Tạo ID duy nhất cho mỗi yêu cầu
90
+ cfg := LoadConfig() // Tải cấu hình (có thể tối ưu hóa để không tải mỗi lần)
91
  startTime := time.Now()
92
 
93
  var claudeReq ClaudeRequest
94
  bodyBytes, err := io.ReadAll(c.Request.Body)
95
  if err != nil {
 
96
  log.Printf("ERROR: [%s] Không thể đọc nội dung yêu cầu: %v", requestID, err)
97
  sendClaudeError(c, http.StatusBadRequest, "invalid_request_error", "Không thể đọc nội dung yêu cầu.")
98
  return
99
  }
100
+ c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // Khôi phục body để có thể đọc lại nếu cần
101
 
102
  if err := json.Unmarshal(bodyBytes, &claudeReq); err != nil {
 
103
  log.Printf("ERROR: [%s] Không thể giải mã JSON yêu cầu: %v. Body: %s", requestID, err, string(bodyBytes))
104
  sendClaudeError(c, http.StatusBadRequest, "invalid_request_error", "Định dạng JSON không hợp lệ trong nội dung yêu cầu.")
105
  return
 
108
  isStreaming := claudeReq.Stream
109
  modelRequested := claudeReq.Model
110
  if modelRequested == "" {
111
+ modelRequested = "unknown_model" // Đặt tên model mặc định nếu client không cung cấp
112
  }
 
113
  log.Printf("INFO: [%s] Nhận yêu cầu. Stream: %t, Model: %s, ClientIP: %s, NativeClaude: %t",
114
  requestID, isStreaming, modelRequested, c.ClientIP(), cfg.UpstreamNativeClaudeFormat)
115
 
116
  var upstreamURL string
117
  var upstreamRequestBodyBytes []byte
118
+ var openAIReqForLogging *OpenAIRequest // Chỉ dùng để log model nếu không phải native Claude
119
 
120
  if cfg.UpstreamNativeClaudeFormat {
121
+ // Upstream API hỗ trợ định dạng Claude native
122
+ upstreamURL = strings.TrimRight(cfg.OpenAIAPIEndpoint, "/") + "/v1/messages" // Đường dẫn chuẩn của Claude API
123
+ upstreamRequestBodyBytes = bodyBytes // Sử dụng body gốc của Claude
 
124
  log.Printf("INFO: [%s] Upstream là Claude native. Chuyển tiếp yêu cầu gốc đến %s", requestID, maskURL(upstreamURL))
125
  } else {
126
+ // Upstream API OpenAI hoặc tương thích OpenAI, cần chuyển đổi
127
+ upstreamURL = strings.TrimRight(cfg.OpenAIAPIEndpoint, "/") + "/v1/chat/completions" // Đường dẫn chuẩn của OpenAI API
128
  openAIReq, errConv := convertClaudeRequestToOpenAI(&claudeReq)
129
  if errConv != nil {
 
130
  log.Printf("ERROR: [%s] Không thể chuyển đổi yêu cầu Claude sang OpenAI: %v", requestID, errConv)
131
  sendClaudeError(c, http.StatusBadRequest, "invalid_request_error", fmt.Sprintf("Lỗi chuyển đổi dữ liệu yêu cầu: %v", errConv))
132
  return
133
  }
134
+ openAIReq.Stream = isStreaming // Đảm bảo cờ stream được đặt chính xác
135
+ openAIReqForLogging = openAIReq // Lưu lại để log model được sử dụng trong SSE
136
 
137
  openaiReqBytesMarshal, errMarshal := json.Marshal(openAIReq)
138
  if errMarshal != nil {
 
139
  log.Printf("ERROR: [%s] Không thể marshal yêu cầu OpenAI JSON: %v", requestID, errMarshal)
140
  sendClaudeError(c, http.StatusInternalServerError, "internal_server_error", "Không thể chuẩn bị yêu cầu upstream.")
141
  return
142
  }
143
  upstreamRequestBodyBytes = openaiReqBytesMarshal
 
144
  log.Printf("INFO: [%s] Upstream là OpenAI. Chuyển đổi và gửi yêu cầu đến %s", requestID, maskURL(upstreamURL))
145
  }
146
 
147
+ // Tạo yêu cầu HTTP mới đến upstream API
148
  req, err := http.NewRequestWithContext(c.Request.Context(), "POST", upstreamURL, bytes.NewBuffer(upstreamRequestBodyBytes))
149
  if err != nil {
 
150
  log.Printf("ERROR: [%s] Không thể tạo yêu cầu HTTP upstream: %v", requestID, err)
151
  sendClaudeError(c, http.StatusInternalServerError, "internal_server_error", "Không thể tạo yêu cầu upstream.")
152
  return
153
  }
154
 
155
+ // Thiết lập các header cho yêu cầu upstream
156
  req.Header.Set("Content-Type", "application/json")
157
  if isStreaming {
158
  req.Header.Set("Accept", "text/event-stream")
159
  } else {
160
  req.Header.Set("Accept", "application/json")
161
  }
162
+
163
+ // Xử lý xác thực cho upstream API
164
+ if cfg.OpenAIAPIKey != "" { // Đảm bảo có API key để gửi
165
+ if cfg.UpstreamNativeClaudeFormat {
166
+ // Upstream Claude native, sử dụng x-api-key anthropic-version
167
+ req.Header.Set("x-api-key", cfg.OpenAIAPIKey)
168
+
169
+ anthropicVersion := os.Getenv(ANTHROPIC_VERSION_ENV_VAR)
170
+ if anthropicVersion == "" {
171
+ anthropicVersion = DEFAULT_ANTHROPIC_VERSION // Sử dụng giá trị mặc định nếu không được đặt
172
+ log.Printf("INFO: [%s] Biến môi trường %s không được đặt, sử dụng phiên bản Anthropic mặc định: %s", requestID, ANTHROPIC_VERSION_ENV_VAR, anthropicVersion)
173
+ }
174
+ req.Header.Set("anthropic-version", anthropicVersion)
175
+ log.Printf("INFO: [%s] Đặt header Anthropic-Version: %s cho upstream Claude native.", requestID, anthropicVersion)
176
+
177
+ } else {
178
+ // Upstream là OpenAI hoặc tương thích OpenAI, sử dụng Bearer token
179
+ req.Header.Set("Authorization", "Bearer "+cfg.OpenAIAPIKey)
180
+ }
181
+ } else {
182
+ log.Printf("WARN: [%s] OPENAI_API_KEY không được cấu hình. Yêu cầu upstream sẽ không được xác thực.", requestID)
183
  }
184
 
 
185
  log.Printf("INFO: [%s] Gửi yêu cầu upstream (Stream=%t) đến %s...", requestID, isStreaming, maskURL(upstreamURL))
186
 
187
+ // Thực hiện yêu cầu HTTP đến upstream API
188
  httpClient := &http.Client{
189
+ Transport: cfg.UpstreamTransport, // Sử dụng transport đã được cấu hình (timeout, proxy, etc.)
190
+ Timeout: 0, // Timeout được quản bởi Transport
191
  }
192
  upstreamResp, err := httpClient.Do(req)
193
  if err != nil {
 
194
  log.Printf("ERROR: [%s] Yêu cầu upstream thất bại: %v", requestID, err)
195
  if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
196
  sendClaudeError(c, http.StatusGatewayTimeout, "api_error", fmt.Sprintf("Gateway Timeout khi kết nối upstream (%v).", cfg.ConnectTimeout))
 
199
  }
200
  return
201
  }
 
202
  log.Printf("INFO: [%s] Nhận trạng thái upstream: %d (%s)", requestID, upstreamResp.StatusCode, http.StatusText(upstreamResp.StatusCode))
203
 
204
+ // Xử lý phản hồi lỗi từ upstream API
205
  if upstreamResp.StatusCode != http.StatusOK {
206
  errorBodyBytes, readErr := io.ReadAll(upstreamResp.Body)
207
  if readErr != nil {
 
208
  log.Printf("WARN: [%s] Không thể đọc nội dung lỗi upstream (Status %d): %v", requestID, upstreamResp.StatusCode, readErr)
209
  }
210
+ _ = upstreamResp.Body.Close() // Luôn đóng body của phản hồi
211
 
212
  errorBodyStr := string(errorBodyBytes)
 
213
  log.Printf("ERROR: [%s] Upstream trả về lỗi %d. Chi tiết ngắn: %s", requestID, upstreamResp.StatusCode, truncateString(strings.TrimSpace(errorBodyStr), 100))
214
 
215
  var errorType string
 
230
  return
231
  }
232
 
233
+ // Xử lý phản hồi thành công từ upstream API
234
  if isStreaming {
235
  if cfg.UpstreamNativeClaudeFormat {
 
236
  log.Printf("INFO: [%s] Upstream Claude native stream nhận được. Bắt đầu proxy SSE (với hack usage).", requestID)
237
+ proxyClaudeNativeSSE(c, upstreamResp, requestID, &claudeReq)
238
  } else {
 
239
  log.Printf("INFO: [%s] Upstream OpenAI stream nhận được. Bắt đầu chuyển đổi SSE.", requestID)
240
+ modelForConversion := claudeReq.Model // Model gốc từ yêu cầu client
241
  if openAIReqForLogging != nil && openAIReqForLogging.Model != "" {
242
+ modelForConversion = openAIReqForLogging.Model // Model đã được chuyển đổi (nếu có)
243
  }
244
  streamOpenAIResponseToClaudeSSE(c, upstreamResp, requestID, modelForConversion, &claudeReq)
245
  }
246
  } else { // Non-streaming
 
247
  log.Printf("INFO: [%s] Phản hồi non-stream upstream nhận được. Đang xử lý.", requestID)
248
+ defer upstreamResp.Body.Close() // Đảm bảo body được đóng sau khi xử lý
249
 
250
  respBodyBytes, errRead := io.ReadAll(upstreamResp.Body)
251
  if errRead != nil {
 
252
  log.Printf("ERROR: [%s] Không thể đọc nội dung phản hồi non-streaming upstream: %v", requestID, errRead)
253
  sendClaudeError(c, http.StatusBadGateway, "api_error", "Không thể đọc phản hồi upstream.")
254
  return
255
  }
256
 
257
  if cfg.UpstreamNativeClaudeFormat {
 
258
  log.Printf("INFO: [%s] Upstream Claude native non-stream response nhận được. Chuyển tiếp trực tiếp.", requestID)
259
+ var tempClaudeResp map[string]interface{} // Kiểm tra xem có phải JSON hợp lệ không
260
  if errJson := json.Unmarshal(respBodyBytes, &tempClaudeResp); errJson != nil {
 
261
  log.Printf("ERROR: [%s] Upstream Claude native response không phải JSON hợp lệ: %v. Body: %s", requestID, errJson, string(respBodyBytes))
262
  sendClaudeError(c, http.StatusBadGateway, "api_error", "API upstream (Claude native) trả về JSON không hợp lệ.")
263
  return
264
  }
265
  clientContentType := upstreamResp.Header.Get("Content-Type")
266
  if clientContentType == "" {
267
+ clientContentType = "application/json; charset=utf-8" // Mặc định nếu không có
268
  }
269
+ c.Data(http.StatusOK, clientContentType, respBodyBytes) // Gửi dữ liệu thô về client
270
  } else {
 
271
  log.Printf("INFO: [%s] Phản hồi non-stream OpenAI upstream nhận được. Đang chuyển đổi.", requestID)
272
  var openAIResp OpenAIResponse
273
  if errJson := json.Unmarshal(respBodyBytes, &openAIResp); errJson != nil {
 
274
  log.Printf("ERROR: [%s] Không thể giải mã JSON non-streaming OpenAI upstream: %v. Body: %s", requestID, errJson, string(respBodyBytes))
275
  sendClaudeError(c, http.StatusBadGateway, "api_error", "API upstream (OpenAI) trả về JSON không hợp lệ.")
276
  return
 
278
 
279
  claudeRespToClient, errConv := convertOpenAIResponseToClaude(&openAIResp, requestID)
280
  if errConv != nil {
 
281
  log.Printf("ERROR: [%s] Không thể chuyển đổi phản hồi non-streaming OpenAI: %v", requestID, errConv)
282
  sendClaudeError(c, http.StatusInternalServerError, "internal_server_error", fmt.Sprintf("Lỗi xử lý phản hồi upstream: %v", errConv))
283
  return
284
  }
285
  c.JSON(http.StatusOK, claudeRespToClient)
286
  }
 
287
  log.Printf("INFO: [%s] Xử lý thành công yêu cầu non-streaming trong %v", requestID, time.Since(startTime))
288
  }
289
  }
290
 
291
+ // sendClaudeError gửi phản hồi lỗi theo định dạng Claude về client.
292
  func sendClaudeError(c *gin.Context, statusCode int, errorType string, message string) {
293
  errResp := ClaudeErrorResponse{
294
  Type: "error",
 
297
  Message: message,
298
  },
299
  }
300
+ if statusCode < 400 || statusCode > 599 { // Đảm bảo mã trạng thái HTTP hợp lệ cho lỗi
 
301
  log.Printf("WARN: Mã trạng thái không hợp lệ %d được cung cấp cho lỗi, mặc định thành 500.", statusCode)
302
  statusCode = http.StatusInternalServerError
303
  }
304
+ c.AbortWithStatusJSON(statusCode, errResp) // Gửi lỗi và dừng xử lý tiếp theo
305
  }
306
 
307
+ // truncateString rút gọn chuỗi nếu nó dài hơn maxLength.
308
  func truncateString(s string, maxLength int) string {
309
  if len(s) <= maxLength {
310
  return s