antigravity_gateway_compat.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. package service
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "net/http"
  9. "strings"
  10. "time"
  11. "github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
  12. "github.com/Wei-Shaw/sub2api/internal/pkg/apicompat"
  13. "github.com/gin-gonic/gin"
  14. )
  15. type antigravityCompatProtocol uint8
  16. const (
  17. antigravityCompatChatCompletions antigravityCompatProtocol = iota
  18. antigravityCompatResponses
  19. )
  20. const (
  21. // AntigravityCredentialRejectedClientMessage 是可安全返回给客户端的认证修复提示。
  22. AntigravityCredentialRejectedClientMessage = "Antigravity rejected the OAuth credential after refresh; reauthorize the account and verify project_id"
  23. // AntigravityCredentialRejectedReason 标识上游拒绝已刷新 OAuth 凭据。
  24. AntigravityCredentialRejectedReason GatewayFailureReason = "antigravity_oauth_credential_rejected"
  25. )
  26. const antigravityCompatMaxTokens = 64000
  27. type antigravityCompatRequest struct {
  28. protocol antigravityCompatProtocol
  29. originalBody []byte
  30. claudeBody []byte
  31. originalModel string
  32. clientStream bool
  33. includeUsage bool
  34. startTime time.Time
  35. reasoningEffort *string
  36. }
  37. type antigravityCompatUpstreamCall struct {
  38. request antigravityCompatRequest
  39. billingModel string
  40. prefix string
  41. proxyURL string
  42. accessToken string
  43. geminiBody []byte
  44. }
  45. // ForwardAsChatCompletions 使用 Antigravity 原生 OAuth 账号转发 Chat Completions 请求。
  46. func (s *AntigravityGatewayService) ForwardAsChatCompletions(
  47. ctx context.Context,
  48. c *gin.Context,
  49. account *Account,
  50. body []byte,
  51. _ *ParsedRequest,
  52. ) (*ForwardResult, error) {
  53. if err := s.validateAntigravityCompatAccount(c, account); err != nil {
  54. return nil, err
  55. }
  56. var request apicompat.ChatCompletionsRequest
  57. if json.Unmarshal(body, &request) != nil {
  58. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
  59. }
  60. if strings.TrimSpace(request.Model) == "" {
  61. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", "model is required")
  62. }
  63. responsesRequest, err := apicompat.ChatCompletionsToResponses(&request)
  64. if err != nil {
  65. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", err.Error())
  66. }
  67. claudeRequest, err := apicompat.ResponsesToAnthropicRequest(responsesRequest)
  68. if err != nil {
  69. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", err.Error())
  70. }
  71. preserveChatCompletionTokenLimit(&request, claudeRequest)
  72. claudeRequest.Stream = request.Stream
  73. claudeBody, err := json.Marshal(claudeRequest)
  74. if err != nil {
  75. return nil, fmt.Errorf("marshal anthropic request: %w", err)
  76. }
  77. return s.forwardAntigravityCompat(ctx, c, account, antigravityCompatRequest{
  78. protocol: antigravityCompatChatCompletions,
  79. originalBody: body,
  80. claudeBody: claudeBody,
  81. originalModel: request.Model,
  82. clientStream: request.Stream,
  83. includeUsage: request.StreamOptions != nil && request.StreamOptions.IncludeUsage,
  84. startTime: time.Now(),
  85. reasoningEffort: extractCCReasoningEffortFromBody(body),
  86. })
  87. }
  88. // ForwardAsResponses 使用 Antigravity 原生 OAuth 账号转发 Responses 请求。
  89. func (s *AntigravityGatewayService) ForwardAsResponses(
  90. ctx context.Context,
  91. c *gin.Context,
  92. account *Account,
  93. body []byte,
  94. _ *ParsedRequest,
  95. ) (*ForwardResult, error) {
  96. if err := s.validateAntigravityCompatAccount(c, account); err != nil {
  97. return nil, err
  98. }
  99. var request apicompat.ResponsesRequest
  100. if json.Unmarshal(body, &request) != nil {
  101. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
  102. }
  103. if strings.TrimSpace(request.Model) == "" {
  104. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", "model is required")
  105. }
  106. claudeRequest, err := apicompat.ResponsesToAnthropicRequest(&request)
  107. if err != nil {
  108. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", err.Error())
  109. }
  110. claudeRequest.Stream = request.Stream
  111. claudeBody, err := json.Marshal(claudeRequest)
  112. if err != nil {
  113. return nil, fmt.Errorf("marshal anthropic request: %w", err)
  114. }
  115. return s.forwardAntigravityCompat(ctx, c, account, antigravityCompatRequest{
  116. protocol: antigravityCompatResponses,
  117. originalBody: body,
  118. claudeBody: claudeBody,
  119. originalModel: request.Model,
  120. clientStream: request.Stream,
  121. startTime: time.Now(),
  122. reasoningEffort: ExtractResponsesReasoningEffortFromBody(body),
  123. })
  124. }
  125. func (s *AntigravityGatewayService) validateAntigravityCompatAccount(c *gin.Context, account *Account) error {
  126. if account != nil && account.Platform == PlatformAntigravity && account.Type == AccountTypeOAuth {
  127. return nil
  128. }
  129. return s.writeAntigravityCompatError(
  130. c,
  131. http.StatusBadRequest,
  132. "invalid_request_error",
  133. "native OAuth account required for antigravity compatibility mode",
  134. )
  135. }
  136. func preserveChatCompletionTokenLimit(request *apicompat.ChatCompletionsRequest, claudeRequest *apicompat.AnthropicRequest) {
  137. if request == nil || claudeRequest == nil {
  138. return
  139. }
  140. limit := request.MaxTokens
  141. if request.MaxCompletionTokens != nil {
  142. limit = request.MaxCompletionTokens
  143. }
  144. if limit != nil && *limit > 0 {
  145. claudeRequest.MaxTokens = min(*limit, antigravityCompatMaxTokens)
  146. }
  147. }
  148. func (s *AntigravityGatewayService) forwardAntigravityCompat(
  149. ctx context.Context,
  150. c *gin.Context,
  151. account *Account,
  152. request antigravityCompatRequest,
  153. ) (*ForwardResult, error) {
  154. beginUpstreamResponseModelObservation(c)
  155. call, err := s.prepareAntigravityCompatCall(ctx, c, account, request)
  156. if err != nil {
  157. return nil, err
  158. }
  159. result, err := s.antigravityRetryLoop(antigravityRetryLoopParams{
  160. ctx: ctx,
  161. prefix: call.prefix,
  162. account: account,
  163. proxyURL: call.proxyURL,
  164. accessToken: call.accessToken,
  165. action: "streamGenerateContent",
  166. body: call.geminiBody,
  167. c: c,
  168. httpUpstream: s.httpUpstream,
  169. settingService: s.settingService,
  170. accountRepo: s.accountRepo,
  171. handleError: s.handleUpstreamError,
  172. requestedModel: request.originalModel,
  173. isStickySession: false,
  174. groupID: 0,
  175. sessionHash: "",
  176. })
  177. if err != nil {
  178. return nil, s.handleAntigravityCompatTransportError(c, err)
  179. }
  180. return s.consumeAntigravityCompatResponse(ctx, c, account, call, result.resp)
  181. }
  182. func (s *AntigravityGatewayService) prepareAntigravityCompatCall(
  183. ctx context.Context,
  184. c *gin.Context,
  185. account *Account,
  186. request antigravityCompatRequest,
  187. ) (*antigravityCompatUpstreamCall, error) {
  188. var claudeRequest antigravity.ClaudeRequest
  189. if json.Unmarshal(request.claudeBody, &claudeRequest) != nil {
  190. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", "Invalid request body")
  191. }
  192. mappedModel := s.getMappedModel(account, request.originalModel)
  193. if mappedModel == "" {
  194. MarkOpsClientBusinessLimited(c, OpsClientBusinessLimitedReasonLocalFeatureGate)
  195. message := fmt.Sprintf("model %s not in whitelist", request.originalModel)
  196. return nil, s.writeAntigravityCompatError(c, http.StatusForbidden, "permission_error", message)
  197. }
  198. thinkingEnabled := claudeRequest.Thinking != nil &&
  199. (claudeRequest.Thinking.Type == "enabled" || claudeRequest.Thinking.Type == "adaptive")
  200. mappedModel = applyThinkingModelSuffix(mappedModel, thinkingEnabled)
  201. if s.tokenProvider == nil {
  202. return nil, s.writeAntigravityCompatError(c, http.StatusBadGateway, "api_error", "Antigravity token provider not configured")
  203. }
  204. accessToken, err := s.tokenProvider.GetAccessToken(ctx, account)
  205. if err != nil {
  206. return nil, &UpstreamFailoverError{
  207. StatusCode: http.StatusBadGateway,
  208. ResponseBody: []byte(`{"error":{"type":"authentication_error","message":"Failed to get upstream access token"},"type":"error"}`),
  209. }
  210. }
  211. projectID, err := resolveAntigravityProjectID(account)
  212. if err != nil {
  213. _ = s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", err.Error())
  214. return nil, err
  215. }
  216. geminiBody, err := s.buildAntigravityCompatGeminiBody(ctx, request.claudeBody, &claudeRequest, projectID, mappedModel)
  217. if err != nil {
  218. return nil, s.writeAntigravityCompatError(c, http.StatusBadRequest, "invalid_request_error", "Invalid request")
  219. }
  220. request.reasoningEffort = ApplyThinkingEnabledFallback(request.reasoningEffort, request.originalBody, mappedModel)
  221. return &antigravityCompatUpstreamCall{
  222. request: request,
  223. billingModel: mappedModel,
  224. prefix: logPrefix(getSessionID(c), account.Name),
  225. proxyURL: antigravityCompatProxyURL(account),
  226. accessToken: accessToken,
  227. geminiBody: geminiBody,
  228. }, nil
  229. }
  230. func (s *AntigravityGatewayService) buildAntigravityCompatGeminiBody(
  231. ctx context.Context,
  232. claudeBody []byte,
  233. claudeRequest *antigravity.ClaudeRequest,
  234. projectID string,
  235. mappedModel string,
  236. ) ([]byte, error) {
  237. if strings.HasPrefix(strings.ToLower(mappedModel), "gemini-") {
  238. body, err := convertClaudeMessagesToGeminiGenerateContent(claudeBody)
  239. if err != nil {
  240. return nil, err
  241. }
  242. body, err = enableMixedGeminiToolInvocations(body)
  243. if err != nil {
  244. return nil, err
  245. }
  246. body = ensureGeminiFunctionCallThoughtSignatures(body)
  247. body, err = injectIdentityPatchToGeminiRequest(body)
  248. if err != nil {
  249. return nil, err
  250. }
  251. if cleaned, cleanErr := cleanGeminiRequest(body); cleanErr == nil {
  252. body = cleaned
  253. }
  254. return s.wrapV1InternalRequest(projectID, mappedModel, body)
  255. }
  256. options := s.getClaudeTransformOptions(ctx)
  257. options.EnableIdentityPatch = true
  258. return antigravity.TransformClaudeToGeminiWithOptions(claudeRequest, projectID, mappedModel, options)
  259. }
  260. func enableMixedGeminiToolInvocations(body []byte) ([]byte, error) {
  261. var request map[string]any
  262. if err := json.Unmarshal(body, &request); err != nil {
  263. return nil, err
  264. }
  265. var hasGoogleSearch, hasFunctionDeclarations bool
  266. if tools, ok := request["tools"].([]any); ok {
  267. for _, rawTool := range tools {
  268. tool, ok := rawTool.(map[string]any)
  269. if !ok {
  270. continue
  271. }
  272. _, hasSearch := tool["googleSearch"]
  273. declarations, hasFunctions := tool["functionDeclarations"].([]any)
  274. hasGoogleSearch = hasGoogleSearch || hasSearch
  275. hasFunctionDeclarations = hasFunctionDeclarations || hasFunctions && len(declarations) > 0
  276. }
  277. }
  278. if !hasGoogleSearch || !hasFunctionDeclarations {
  279. return body, nil
  280. }
  281. toolConfig, _ := request["toolConfig"].(map[string]any)
  282. if toolConfig == nil {
  283. toolConfig = make(map[string]any)
  284. request["toolConfig"] = toolConfig
  285. }
  286. toolConfig["includeServerSideToolInvocations"] = true
  287. return json.Marshal(request)
  288. }
  289. func antigravityCompatProxyURL(account *Account) string {
  290. if account.ProxyID == nil || account.Proxy == nil {
  291. return ""
  292. }
  293. return account.Proxy.URL()
  294. }
  295. func (s *AntigravityGatewayService) handleAntigravityCompatTransportError(c *gin.Context, err error) error {
  296. if switchErr, ok := IsAntigravityAccountSwitchError(err); ok {
  297. return &UpstreamFailoverError{
  298. StatusCode: http.StatusServiceUnavailable,
  299. ForceCacheBilling: switchErr.IsStickySession,
  300. }
  301. }
  302. if c.Request.Context().Err() != nil {
  303. return s.writeAntigravityCompatError(c, http.StatusBadGateway, "client_disconnected", "Client disconnected before upstream response")
  304. }
  305. return s.writeAntigravityCompatError(c, http.StatusBadGateway, "upstream_error", "Upstream request failed after retries")
  306. }
  307. func (s *AntigravityGatewayService) consumeAntigravityCompatResponse(
  308. ctx context.Context,
  309. c *gin.Context,
  310. account *Account,
  311. call *antigravityCompatUpstreamCall,
  312. resp *http.Response,
  313. ) (*ForwardResult, error) {
  314. defer func() { _ = resp.Body.Close() }()
  315. if resp.StatusCode >= http.StatusBadRequest {
  316. return nil, s.handleAntigravityCompatHTTPError(ctx, c, account, call, resp)
  317. }
  318. requestID := resp.Header.Get("x-request-id")
  319. if requestID != "" {
  320. c.Header("x-request-id", requestID)
  321. }
  322. streamResult, err := s.consumeAntigravityCompatSuccess(c, call, resp)
  323. if err != nil {
  324. return nil, err
  325. }
  326. if streamResult.usage == nil {
  327. streamResult.usage = &ClaudeUsage{}
  328. }
  329. return &ForwardResult{
  330. RequestID: requestID,
  331. UpstreamHeaders: resp.Header,
  332. Usage: *streamResult.usage,
  333. Model: call.request.originalModel,
  334. UpstreamModel: call.billingModel,
  335. UpstreamResponseModel: observedUpstreamResponseModel(c),
  336. UpstreamResponseModelConflict: observedUpstreamResponseModelConflict(c),
  337. Stream: call.request.clientStream,
  338. Duration: time.Since(call.request.startTime),
  339. FirstTokenMs: streamResult.firstTokenMs,
  340. ReasoningEffort: call.request.reasoningEffort,
  341. ClientDisconnect: streamResult.clientDisconnect,
  342. }, nil
  343. }
  344. func (s *AntigravityGatewayService) consumeAntigravityCompatSuccess(
  345. c *gin.Context,
  346. call *antigravityCompatUpstreamCall,
  347. resp *http.Response,
  348. ) (*antigravityStreamResult, error) {
  349. if call.request.clientStream {
  350. if call.request.protocol == antigravityCompatChatCompletions {
  351. return s.handleChatCompletionsStreamingFromAntigravity(
  352. c,
  353. resp,
  354. call.request.startTime,
  355. call.request.originalModel,
  356. call.request.includeUsage,
  357. )
  358. }
  359. return s.handleResponsesStreamingFromAntigravity(c, resp, call.request.startTime, call.request.originalModel)
  360. }
  361. if call.request.protocol == antigravityCompatChatCompletions {
  362. return s.handleChatCompletionsNonStreamingFromAntigravity(c, resp, call.request.startTime, call.request.originalModel)
  363. }
  364. return s.handleResponsesNonStreamingFromAntigravity(c, resp, call.request.startTime, call.request.originalModel)
  365. }
  366. func (s *AntigravityGatewayService) handleAntigravityCompatHTTPError(
  367. ctx context.Context,
  368. c *gin.Context,
  369. account *Account,
  370. call *antigravityCompatUpstreamCall,
  371. resp *http.Response,
  372. ) error {
  373. body := s.readUpstreamErrorBody(resp)
  374. s.handleUpstreamError(
  375. ctx,
  376. call.prefix,
  377. account,
  378. resp.StatusCode,
  379. resp.Header,
  380. body,
  381. call.request.originalModel,
  382. 0,
  383. "",
  384. false,
  385. )
  386. if s.shouldFailoverUpstreamError(resp.StatusCode) {
  387. message := sanitizeUpstreamErrorMessage(strings.TrimSpace(extractAntigravityErrorMessage(body)))
  388. event := OpsUpstreamErrorEvent{
  389. ProxyID: opsUpstreamProxyID(account),
  390. ProxyName: opsUpstreamProxyName(account),
  391. Platform: account.Platform,
  392. AccountID: account.ID,
  393. AccountName: account.Name,
  394. UpstreamStatusCode: resp.StatusCode,
  395. UpstreamRequestID: resp.Header.Get("x-request-id"),
  396. Kind: "failover",
  397. Message: message,
  398. Detail: s.getUpstreamErrorDetail(body),
  399. }
  400. if resp.StatusCode == http.StatusUnauthorized {
  401. event.Stage = string(GatewayFailureStageAccountAuth)
  402. event.Scope = string(GatewayFailureScopeAccount)
  403. event.Reason = string(AntigravityCredentialRejectedReason)
  404. appendOpsUpstreamError(c, event)
  405. return antigravityCredentialRejectedError(resp, body)
  406. }
  407. appendOpsUpstreamError(c, event)
  408. return &UpstreamFailoverError{
  409. StatusCode: resp.StatusCode,
  410. ResponseBody: body,
  411. ResponseHeaders: resp.Header.Clone(),
  412. }
  413. }
  414. return s.writeMappedAntigravityCompatError(c, account, resp.StatusCode, resp.Header.Get("x-request-id"), body)
  415. }
  416. func antigravityCredentialRejectedError(resp *http.Response, body []byte) *UpstreamFailoverError {
  417. return &UpstreamFailoverError{
  418. StatusCode: resp.StatusCode,
  419. ResponseBody: body,
  420. ResponseHeaders: resp.Header.Clone(),
  421. Stage: GatewayFailureStageAccountAuth,
  422. Scope: GatewayFailureScopeAccount,
  423. Reason: AntigravityCredentialRejectedReason,
  424. NextAccountAction: NextAccountRetry,
  425. ClientStatusCode: http.StatusBadGateway,
  426. ClientMessage: AntigravityCredentialRejectedClientMessage,
  427. }
  428. }
  429. func (s *AntigravityGatewayService) writeAntigravityCompatError(
  430. c *gin.Context,
  431. status int,
  432. errType string,
  433. message string,
  434. ) error {
  435. MarkResponseCommitted(c)
  436. c.JSON(status, gin.H{
  437. "error": gin.H{
  438. "message": message,
  439. "type": errType,
  440. "param": nil,
  441. "code": nil,
  442. },
  443. })
  444. return errors.New(message)
  445. }
  446. func (s *AntigravityGatewayService) writeMappedAntigravityCompatError(
  447. c *gin.Context,
  448. account *Account,
  449. upstreamStatus int,
  450. upstreamRequestID string,
  451. body []byte,
  452. ) error {
  453. MarkResponseCommitted(c)
  454. message := sanitizeUpstreamErrorMessage(strings.TrimSpace(extractAntigravityErrorMessage(body)))
  455. setOpsUpstreamError(c, upstreamStatus, message, s.getUpstreamErrorDetail(body))
  456. appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
  457. ProxyID: opsUpstreamProxyID(account),
  458. ProxyName: opsUpstreamProxyName(account),
  459. Platform: account.Platform,
  460. AccountID: account.ID,
  461. AccountName: account.Name,
  462. UpstreamStatusCode: upstreamStatus,
  463. UpstreamRequestID: upstreamRequestID,
  464. Kind: "http_error",
  465. Message: message,
  466. })
  467. c.JSON(mapUpstreamStatusCode(upstreamStatus), gin.H{
  468. "error": gin.H{
  469. "message": getPassthroughOrDefault(message, "Upstream request failed"),
  470. "type": "upstream_error",
  471. "param": nil,
  472. "code": nil,
  473. },
  474. })
  475. return fmt.Errorf("upstream error: %d %s", upstreamStatus, message)
  476. }
  477. func (s *AntigravityGatewayService) handleChatCompletionsNonStreamingFromAntigravity(
  478. c *gin.Context,
  479. resp *http.Response,
  480. startTime time.Time,
  481. originalModel string,
  482. ) (*antigravityStreamResult, error) {
  483. claudeResponse, result, err := s.collectClaudeStreamResponse(c, resp, startTime, originalModel)
  484. if err != nil {
  485. return nil, s.mapAntigravityCompatCollectionError(c, err)
  486. }
  487. var anthropicResponse apicompat.AnthropicResponse
  488. if json.Unmarshal(claudeResponse, &anthropicResponse) != nil {
  489. return nil, s.writeAntigravityCompatError(c, http.StatusBadGateway, "upstream_error", "Failed to parse upstream response")
  490. }
  491. responsesResponse := apicompat.AnthropicToResponsesResponse(&anthropicResponse)
  492. c.JSON(http.StatusOK, apicompat.ResponsesToChatCompletions(responsesResponse, originalModel))
  493. return result, nil
  494. }
  495. func (s *AntigravityGatewayService) handleResponsesNonStreamingFromAntigravity(
  496. c *gin.Context,
  497. resp *http.Response,
  498. startTime time.Time,
  499. originalModel string,
  500. ) (*antigravityStreamResult, error) {
  501. claudeResponse, result, err := s.collectClaudeStreamResponse(c, resp, startTime, originalModel)
  502. if err != nil {
  503. return nil, s.mapAntigravityCompatCollectionError(c, err)
  504. }
  505. var anthropicResponse apicompat.AnthropicResponse
  506. if json.Unmarshal(claudeResponse, &anthropicResponse) != nil {
  507. return nil, s.writeAntigravityCompatError(c, http.StatusBadGateway, "upstream_error", "Failed to parse upstream response")
  508. }
  509. c.JSON(http.StatusOK, apicompat.AnthropicToResponsesResponse(&anthropicResponse))
  510. return result, nil
  511. }
  512. func (s *AntigravityGatewayService) mapAntigravityCompatCollectionError(c *gin.Context, err error) error {
  513. var failoverError *UpstreamFailoverError
  514. if errors.As(err, &failoverError) {
  515. return err
  516. }
  517. if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
  518. return err
  519. }
  520. if strings.Contains(err.Error(), "stream data interval timeout") {
  521. return s.writeAntigravityCompatError(c, http.StatusBadGateway, "upstream_timeout", "Upstream stream data interval timeout")
  522. }
  523. if errors.Is(err, bufio.ErrTooLong) {
  524. return s.writeAntigravityCompatError(c, http.StatusBadGateway, "response_too_large", "Upstream response line too long")
  525. }
  526. return s.writeAntigravityCompatError(c, http.StatusBadGateway, "upstream_error", "Failed to parse upstream response")
  527. }