@fudrouter/fsrouter 0.6.72 → 0.6.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/headroom/detect.js +97 -7
- package/dist/lib/headroom/process.js +136 -4
- package/dist/open-sse/config/appConstants.js +23 -79
- package/dist/open-sse/config/cliFingerprints.js +1 -1
- package/dist/open-sse/config/errorConfig.js +0 -11
- package/dist/open-sse/config/grokCli.js +10 -0
- package/dist/open-sse/config/kiroConstants.js +99 -46
- package/dist/open-sse/config/runtimeConfig.js +2 -0
- package/dist/open-sse/config/ttsModels.js +2 -0
- package/dist/open-sse/executors/antigravity.js +250 -164
- package/dist/open-sse/executors/base.js +16 -11
- package/dist/open-sse/executors/codex.js +269 -1108
- package/dist/open-sse/executors/commandCode.js +1 -1
- package/dist/open-sse/executors/commandcode.js +10 -4
- package/dist/open-sse/executors/cursor.js +96 -203
- package/dist/open-sse/executors/default.js +146 -244
- package/dist/open-sse/executors/github.js +128 -73
- package/dist/open-sse/executors/grok-cli.js +527 -94
- package/dist/open-sse/executors/grok-web.js +5 -7
- package/dist/open-sse/executors/index.js +12 -0
- package/dist/open-sse/executors/kiro.js +150 -61
- package/dist/open-sse/executors/opencode-go.js +12 -4
- package/dist/open-sse/executors/opencode.js +1 -1
- package/dist/open-sse/executors/perplexity-web.js +20 -96
- package/dist/open-sse/executors/qoder.js +28 -18
- package/dist/open-sse/executors/vertex.js +53 -7
- package/dist/open-sse/executors/xiaomi-tokenplan.js +6 -5
- package/dist/open-sse/handlers/chatCore/nonStreamingHandler.js +97 -85
- package/dist/open-sse/handlers/chatCore/requestDetail.js +32 -8
- package/dist/open-sse/handlers/chatCore/sseToJsonHandler.js +13 -7
- package/dist/open-sse/handlers/chatCore/streamingHandler.js +74 -25
- package/dist/open-sse/handlers/chatCore.js +35 -41
- package/dist/open-sse/handlers/embeddingProviders/index.js +1 -0
- package/dist/open-sse/handlers/embeddingProviders/openai.js +8 -16
- package/dist/open-sse/handlers/imageProviders/blackForestLabs.js +2 -21
- package/dist/open-sse/handlers/imageProviders/cloudflareAi.js +2 -30
- package/dist/open-sse/handlers/imageProviders/codex.js +2 -20
- package/dist/open-sse/handlers/imageProviders/comfyui.js +5 -18
- package/dist/open-sse/handlers/imageProviders/falAi.js +2 -22
- package/dist/open-sse/handlers/imageProviders/gemini.js +2 -16
- package/dist/open-sse/handlers/imageProviders/huggingface.js +2 -17
- package/dist/open-sse/handlers/imageProviders/index.js +3 -4
- package/dist/open-sse/handlers/imageProviders/leonardo.js +1 -1
- package/dist/open-sse/handlers/imageProviders/nanobanana.js +4 -23
- package/dist/open-sse/handlers/imageProviders/openai.js +15 -43
- package/dist/open-sse/handlers/imageProviders/runwayml.js +2 -26
- package/dist/open-sse/handlers/imageProviders/sdwebui.js +4 -20
- package/dist/open-sse/handlers/imageProviders/stabilityAi.js +2 -26
- package/dist/open-sse/handlers/imageProviders/weavy.js +1 -1
- package/dist/open-sse/handlers/search/chatSearch.js +60 -14
- package/dist/open-sse/handlers/ttsProviders/gemini.js +14 -3
- package/dist/open-sse/handlers/ttsProviders/index.js +4 -2
- package/dist/open-sse/handlers/ttsProviders/openai.js +4 -1
- package/dist/open-sse/handlers/ttsProviders/openrouter.js +7 -4
- package/dist/open-sse/handlers/videoCore.js +166 -0
- package/dist/open-sse/handlers/videoProviders/weavy.js +1 -1
- package/dist/open-sse/mcp-server/catalog.js +1 -1
- package/dist/open-sse/mcp-server/server.js +2 -2
- package/dist/open-sse/mcp-server/tools/advancedTools.js +2 -2
- package/dist/open-sse/mcp-server/tools/agentSkillTools.js +1 -1
- package/dist/open-sse/mcp-server/tools/compressionTools.js +4 -4
- package/dist/open-sse/mcp-server/tools/gamificationTools.js +10 -10
- package/dist/open-sse/mcp-server/tools/githubSkillTools.js +1 -1
- package/dist/open-sse/mcp-server/tools/memoryTools.js +3 -3
- package/dist/open-sse/mcp-server/tools/notionTools.js +2 -2
- package/dist/open-sse/mcp-server/tools/obsidianTools.js +2 -2
- package/dist/open-sse/mcp-server/tools/pickFastestModel.js +1 -1
- package/dist/open-sse/mcp-server/tools/pluginTools.js +4 -4
- package/dist/open-sse/mcp-server/tools/skillTools.js +2 -2
- package/dist/open-sse/providers/capabilities.js +24 -4
- package/dist/open-sse/providers/registry/alicode-intl.js +1 -1
- package/dist/open-sse/providers/registry/anthropic.js +1 -8
- package/dist/open-sse/providers/registry/codex.js +0 -9
- package/dist/open-sse/providers/registry/cohere.js +0 -2
- package/dist/open-sse/providers/registry/gemini.js +0 -2
- package/dist/open-sse/providers/registry/github.js +9 -6
- package/dist/open-sse/providers/registry/grok-cli.js +28 -18
- package/dist/open-sse/providers/registry/index.js +74 -80
- package/dist/open-sse/providers/registry/kiro.js +12 -0
- package/dist/open-sse/providers/registry/mistral.js +0 -3
- package/dist/open-sse/providers/registry/openai.js +0 -3
- package/dist/open-sse/providers/registry/perplexity.js +0 -2
- package/dist/open-sse/providers/registry/xai.js +5 -3
- package/dist/open-sse/rtk/autodetect.js +10 -2
- package/dist/open-sse/rtk/caveman.js +2 -93
- package/dist/open-sse/rtk/cavemanPrompts.js +34 -2
- package/dist/open-sse/rtk/constants.js +7 -2
- package/dist/open-sse/rtk/headroom.js +133 -0
- package/dist/open-sse/rtk/ponytail.js +1 -1
- package/dist/open-sse/rtk/registry.js +2 -0
- package/dist/open-sse/services/accountFallback.js +0 -108
- package/dist/open-sse/services/combo/quotaExhaustionCutoff.js +2 -2
- package/dist/open-sse/services/combo/quotaShareStrategy.js +1 -1
- package/dist/open-sse/services/combo/resolveAutoStrategy.js +1 -1
- package/dist/open-sse/services/combo/sessionStickiness.js +1 -1
- package/dist/open-sse/services/combo/targetSorters.js +2 -2
- package/dist/open-sse/services/combo/validateQuality.js +1 -1
- package/dist/open-sse/services/comboConfig.js +1 -1
- package/dist/open-sse/services/emergencyFallback.js +1 -1
- package/dist/open-sse/services/grokCliModels.js +127 -0
- package/dist/open-sse/services/model.js +7 -1
- package/dist/open-sse/services/provider.js +1 -1
- package/dist/open-sse/services/proxyAutoSelector.js +1 -1
- package/dist/open-sse/services/tokenRefresh/providers.js +3 -3
- package/dist/open-sse/services/usage/codex.js +148 -214
- package/dist/open-sse/services/usage/grok-cli.js +61 -7
- package/dist/open-sse/services/usage/misc.js +0 -19
- package/dist/open-sse/services/usage.js +0 -2
- package/dist/open-sse/translator/concerns/paramSupport.js +2 -2
- package/dist/open-sse/translator/concerns/thinkingUnified.js +6 -0
- package/dist/open-sse/translator/index.js +97 -59
- package/dist/open-sse/translator/request/claude-to-kiro.js +67 -40
- package/dist/open-sse/translator/request/openai-responses.js +1 -0
- package/dist/open-sse/translator/request/openai-to-commandcode.js +0 -4
- package/dist/open-sse/translator/request/openai-to-kiro.js +49 -20
- package/dist/open-sse/translator/response/commandcode-to-openai.js +0 -4
- package/dist/open-sse/utils/bypassHandler.js +18 -3
- package/dist/open-sse/utils/claudeCloaking.js +15 -5
- package/dist/open-sse/utils/cursorImages.js +1 -1
- package/dist/open-sse/utils/error.js +0 -17
- package/dist/open-sse/utils/keepaliveThreshold.js +4 -4
- package/dist/open-sse/utils/kiroSessionReplay.js +125 -0
- package/dist/open-sse/utils/noThinkingAlias.js +1 -1
- package/dist/open-sse/utils/proxyFetch.js +0 -1
- package/dist/open-sse/utils/reasoningContentInjector.js +4 -7
- package/dist/open-sse/utils/responsesStatePolicy.js +1 -1
- package/dist/open-sse/utils/responsesStreamHelpers.js +1 -0
- package/dist/open-sse/utils/sessionManager.js +49 -22
- package/dist/open-sse/utils/stream.js +54 -77
- package/dist/open-sse/utils/streamHandler.js +19 -15
- package/dist/open-sse/utils/streamHelpers.js +2 -17
- package/dist/open-sse/utils/usageTracking.js +103 -12
- package/dist/routes/headroom/extras/route.js +58 -0
- package/dist/routes/headroom/restart/route.js +33 -0
- package/open-sse/config/appConstants.js +23 -79
- package/open-sse/config/errorConfig.js +0 -11
- package/open-sse/config/grokCli.js +10 -0
- package/open-sse/config/kiroConstants.js +99 -46
- package/open-sse/config/runtimeConfig.js +2 -0
- package/open-sse/config/ttsModels.js +2 -0
- package/open-sse/executors/antigravity.js +250 -164
- package/open-sse/executors/base.js +16 -11
- package/open-sse/executors/codex.js +269 -1108
- package/open-sse/executors/commandcode.js +10 -4
- package/open-sse/executors/cursor.js +96 -203
- package/open-sse/executors/default.js +146 -244
- package/open-sse/executors/github.js +128 -73
- package/open-sse/executors/grok-cli.js +527 -94
- package/open-sse/executors/grok-web.js +5 -7
- package/open-sse/executors/index.js +12 -0
- package/open-sse/executors/kiro.js +150 -61
- package/open-sse/executors/opencode-go.js +12 -4
- package/open-sse/executors/opencode.js +1 -1
- package/open-sse/executors/perplexity-web.js +20 -96
- package/open-sse/executors/qoder.js +28 -18
- package/open-sse/executors/vertex.js +53 -7
- package/open-sse/executors/xiaomi-tokenplan.js +6 -5
- package/open-sse/handlers/chatCore/nonStreamingHandler.js +97 -85
- package/open-sse/handlers/chatCore/requestDetail.js +32 -8
- package/open-sse/handlers/chatCore/sseToJsonHandler.js +13 -7
- package/open-sse/handlers/chatCore/streamingHandler.js +74 -25
- package/open-sse/handlers/chatCore.js +35 -41
- package/open-sse/handlers/embeddingProviders/index.js +1 -0
- package/open-sse/handlers/embeddingProviders/openai.js +8 -16
- package/open-sse/handlers/imageProviders/blackForestLabs.js +2 -21
- package/open-sse/handlers/imageProviders/cloudflareAi.js +2 -30
- package/open-sse/handlers/imageProviders/codex.js +2 -20
- package/open-sse/handlers/imageProviders/comfyui.js +5 -18
- package/open-sse/handlers/imageProviders/falAi.js +2 -22
- package/open-sse/handlers/imageProviders/gemini.js +2 -16
- package/open-sse/handlers/imageProviders/huggingface.js +2 -17
- package/open-sse/handlers/imageProviders/index.js +3 -4
- package/open-sse/handlers/imageProviders/leonardo.js +1 -1
- package/open-sse/handlers/imageProviders/nanobanana.js +4 -23
- package/open-sse/handlers/imageProviders/openai.js +15 -43
- package/open-sse/handlers/imageProviders/runwayml.js +2 -26
- package/open-sse/handlers/imageProviders/sdwebui.js +4 -20
- package/open-sse/handlers/imageProviders/stabilityAi.js +2 -26
- package/open-sse/handlers/imageProviders/weavy.js +1 -1
- package/open-sse/handlers/search/chatSearch.js +60 -14
- package/open-sse/handlers/ttsProviders/gemini.js +14 -3
- package/open-sse/handlers/ttsProviders/index.js +4 -2
- package/open-sse/handlers/ttsProviders/openai.js +4 -1
- package/open-sse/handlers/ttsProviders/openrouter.js +7 -4
- package/open-sse/handlers/videoCore.js +166 -0
- package/open-sse/handlers/videoProviders/weavy.js +1 -1
- package/open-sse/mcp-server/audit.js +1 -1
- package/open-sse/mcp-server/catalog.js +2 -2
- package/open-sse/mcp-server/descriptionCompressor.js +1 -1
- package/open-sse/mcp-server/mcpCallerIdentity.js +1 -1
- package/open-sse/mcp-server/schemas/tools.js +1 -1
- package/open-sse/mcp-server/server.js +4 -4
- package/open-sse/mcp-server/tools/advancedTools.js +5 -5
- package/open-sse/mcp-server/tools/agentSkillTools.js +1 -1
- package/open-sse/mcp-server/tools/compressionTools.js +4 -4
- package/open-sse/mcp-server/tools/gamificationTools.js +10 -10
- package/open-sse/mcp-server/tools/githubSkillTools.js +1 -1
- package/open-sse/mcp-server/tools/memoryTools.js +3 -3
- package/open-sse/mcp-server/tools/notionTools.js +2 -2
- package/open-sse/mcp-server/tools/obsidianTools.js +2 -2
- package/open-sse/mcp-server/tools/pickFastestModel.js +3 -3
- package/open-sse/mcp-server/tools/pluginTools.js +4 -4
- package/open-sse/mcp-server/tools/skillTools.js +2 -2
- package/open-sse/providers/capabilities.js +24 -4
- package/open-sse/providers/registry/alicode-intl.js +1 -1
- package/open-sse/providers/registry/anthropic.js +1 -8
- package/open-sse/providers/registry/codex.js +0 -9
- package/open-sse/providers/registry/cohere.js +0 -2
- package/open-sse/providers/registry/gemini.js +0 -2
- package/open-sse/providers/registry/github.js +9 -6
- package/open-sse/providers/registry/grok-cli.js +28 -18
- package/open-sse/providers/registry/index.js +74 -80
- package/open-sse/providers/registry/kiro.js +12 -0
- package/open-sse/providers/registry/mistral.js +0 -3
- package/open-sse/providers/registry/openai.js +0 -3
- package/open-sse/providers/registry/perplexity.js +0 -2
- package/open-sse/providers/registry/xai.js +5 -3
- package/open-sse/rtk/autodetect.js +10 -2
- package/open-sse/rtk/caveman.js +2 -93
- package/open-sse/rtk/cavemanPrompts.js +34 -2
- package/open-sse/rtk/constants.js +7 -2
- package/open-sse/rtk/headroom.js +133 -0
- package/open-sse/rtk/ponytail.js +1 -1
- package/open-sse/rtk/registry.js +2 -0
- package/open-sse/services/accountFallback.js +0 -108
- package/open-sse/services/combo/quotaExhaustionCutoff.js +2 -2
- package/open-sse/services/combo/quotaShareStrategy.js +1 -1
- package/open-sse/services/combo/resolveAutoStrategy.js +1 -1
- package/open-sse/services/combo/sessionStickiness.js +1 -1
- package/open-sse/services/combo/shadowRouting.js +1 -1
- package/open-sse/services/combo/targetSorters.js +4 -4
- package/open-sse/services/combo/validateQuality.js +1 -1
- package/open-sse/services/evalRouting.js +1 -1
- package/open-sse/services/grokCliModels.js +127 -0
- package/open-sse/services/model.js +7 -1
- package/open-sse/services/modelCapabilities.js +1 -1
- package/open-sse/services/modelFamilyFallback.js +1 -1
- package/open-sse/services/provider.js +1 -1
- package/open-sse/services/providerCooldownTracker.js +1 -1
- package/open-sse/services/rateLimitManager.js +1 -1
- package/open-sse/services/tokenRefresh/providers.js +3 -3
- package/open-sse/services/usage/codex.js +148 -214
- package/open-sse/services/usage/grok-cli.js +61 -7
- package/open-sse/services/usage/misc.js +0 -19
- package/open-sse/services/usage.js +0 -2
- package/open-sse/translator/concerns/paramSupport.js +2 -2
- package/open-sse/translator/concerns/thinkingUnified.js +6 -0
- package/open-sse/translator/index.js +97 -59
- package/open-sse/translator/request/claude-to-kiro.js +67 -40
- package/open-sse/translator/request/openai-responses.js +1 -0
- package/open-sse/translator/request/openai-to-commandcode.js +0 -4
- package/open-sse/translator/request/openai-to-kiro.js +49 -20
- package/open-sse/translator/response/commandcode-to-openai.js +0 -4
- package/open-sse/utils/bypassHandler.js +18 -3
- package/open-sse/utils/claudeCloaking.js +15 -5
- package/open-sse/utils/error.js +0 -17
- package/open-sse/utils/kiroSessionReplay.js +125 -0
- package/open-sse/utils/logger.js +1 -1
- package/open-sse/utils/proxyFetch.js +0 -1
- package/open-sse/utils/reasoningContentInjector.js +4 -7
- package/open-sse/utils/responsesStreamHelpers.js +1 -0
- package/open-sse/utils/sessionManager.js +49 -22
- package/open-sse/utils/stream.js +54 -77
- package/open-sse/utils/streamHandler.js +19 -15
- package/open-sse/utils/streamHelpers.js +2 -17
- package/open-sse/utils/usageTracking.js +103 -12
- package/package.json +4 -7
- package/public/assets/EndpointPageClient.C2WMTYy0.js +6 -0
- package/public/assets/{Loading.Bj-WGl4k.js → Loading.BHPtQ9vj.js} +1 -1
- package/public/assets/{NoAuthProxyCard.qAv57b-0.js → NoAuthProxyCard.BDcXaWvM.js} +1 -1
- package/public/assets/index.Be3gbrBl.js +89 -0
- package/public/assets/index.COMgQZYj.css +1 -0
- package/public/assets/{page.CbkENBd5.js → page.-lpNgYAw.js} +1 -1
- package/public/assets/{page.CssmrzTT.js → page.11hpNKmq.js} +1 -1
- package/public/assets/{page.C72FTTJr.js → page.B-LxsnqU.js} +1 -1
- package/public/assets/{page.sOFOrxqq.js → page.B-dzeIUC.js} +1 -1
- package/public/assets/{page.CsxWmPoI.js → page.B-nBR75u.js} +1 -1
- package/public/assets/{page.Dx_GRRdT.js → page.B3QqvLep.js} +1 -1
- package/public/assets/{page.DPyOkPVo.js → page.B8xdkCWA.js} +1 -1
- package/public/assets/{page.DUI8kqOY.js → page.BFb7yUd8.js} +1 -1
- package/public/assets/page.BbPPdoas.js +1 -0
- package/public/assets/page.BdS20a1O.js +1 -0
- package/public/assets/{page.DibhF7f-.js → page.BfmI9KYe.js} +1 -1
- package/public/assets/{page.DX4Nno6M.js → page.BgGJovOg.js} +1 -1
- package/public/assets/{page.BnmTC5jG.js → page.C0gBvR4O.js} +1 -1
- package/public/assets/{page.D5gj98Oe.js → page.C6bYizL7.js} +1 -1
- package/public/assets/{page.vla5Mzki.js → page.C8sLEMa6.js} +1 -1
- package/public/assets/{page.CXd_R9yU.js → page.CCNsZBXp.js} +1 -1
- package/public/assets/{page.CQlVGoXg.js → page.CF_YSLwP.js} +1 -1
- package/public/assets/{page.BbO1pspY.js → page.CPTtRDxw.js} +1 -1
- package/public/assets/{page.D1oKew1j.js → page.CdMmlUEm.js} +1 -1
- package/public/assets/{page.DxMDTbSx.js → page.Cged1bOZ.js} +1 -1
- package/public/assets/{page.Cj5h09Zo.js → page.Ch647dVk.js} +1 -1
- package/public/assets/{page.DVIHRbxB.js → page.CpvXDkQF.js} +1 -1
- package/public/assets/{page.i4KDuWcM.js → page.CrBl7f8N.js} +1 -1
- package/public/assets/{page.BSLJ4tx5.js → page.CrP7niA2.js} +1 -1
- package/public/assets/{page.CnTop8Zt.js → page.CvUFZzXr.js} +1 -1
- package/public/assets/{page.O2ddJYoT.js → page.CyUqJPmx.js} +1 -1
- package/public/assets/{page.DBhu4Arl.js → page.D8Rp8fFR.js} +1 -1
- package/public/assets/{page.CPZav1_c.js → page.Df9qtrdG.js} +1 -1
- package/public/assets/{page.Qkkv8roc.js → page.DiL4Gwqe.js} +1 -1
- package/public/assets/{page.BdmJ3yY3.js → page.Dl1apZE2.js} +1 -1
- package/public/assets/{page.1qjqvrwX.js → page.HV0w1d5C.js} +1 -1
- package/public/assets/{page.ChX1NqmC.js → page.N1x4YL8_.js} +1 -1
- package/public/assets/{page.Dh5f0XJC.js → page.P2Ny5veR.js} +1 -1
- package/public/assets/{page.CSs3-gdt.js → page.fOOCr18J.js} +1 -1
- package/public/assets/{page.Ir7KvKcF.js → page.sUIPWIiF.js} +1 -1
- package/public/assets/{page.CzlQTyAL.js → page.tE7PpWYW.js} +1 -1
- package/public/assets/{page.DzZLqI5Q.js → page.uziaGZsT.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/EndpointPageClient.LyNNvZ4z.js +0 -6
- package/public/assets/index.BuPfZebu.css +0 -1
- package/public/assets/index.CShjRV_B.js +0 -89
- package/public/assets/page.DD_czjjP.js +0 -1
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
import { BaseExecutor } from "./base.js";
|
|
3
3
|
import { PROVIDERS } from "../config/providers.js";
|
|
4
|
-
import { OAUTH_ENDPOINTS,
|
|
5
|
-
import { resolveAntigravityVersion } from "../services/antigravityVersion.js";
|
|
6
|
-
import { antigravityUserAgent } from "../services/antigravityHeaders.js";
|
|
4
|
+
import { OAUTH_ENDPOINTS, ANTIGRAVITY_HEADERS, AG_DEFAULT_TOOLS, AG_TOOL_SUFFIX } from "../config/appConstants.js";
|
|
7
5
|
import { HTTP_STATUS } from "../config/runtimeConfig.js";
|
|
8
|
-
import {
|
|
6
|
+
import { resolveSessionId } from "../utils/sessionManager.js";
|
|
9
7
|
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
|
10
|
-
import { cleanJSONSchemaForAntigravity } from "../translator/
|
|
8
|
+
import { cleanJSONSchemaForAntigravity } from "../translator/formats/gemini.js";
|
|
9
|
+
import { DEFAULT_THINKING_AG_SIGNATURE } from "../config/defaultThinkingSignature.js";
|
|
11
10
|
|
|
12
11
|
// Sanitize function name: Gemini requires [a-zA-Z_][a-zA-Z0-9_.:\-]{0,63}
|
|
13
12
|
function sanitizeFunctionName(name) {
|
|
@@ -18,7 +17,97 @@ function sanitizeFunctionName(name) {
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
const MAX_RETRY_AFTER_MS = 10000;
|
|
21
|
-
const
|
|
20
|
+
const ANTIGRAVITY_TRANSIENT_RETRY_MAX_MS = 15000;
|
|
21
|
+
const MAX_ANTIGRAVITY_OUTPUT_TOKENS = 64000;
|
|
22
|
+
const ANTIGRAVITY_IDE_REQUEST_ID_RE = /^agent\/[^/]+\/\d+\/[^/]+\/\d+$/;
|
|
23
|
+
|
|
24
|
+
const ANTIGRAVITY_TRANSIENT_ERROR_PATTERNS = [
|
|
25
|
+
/high\s+traffic/i,
|
|
26
|
+
/agent\s+(execution\s+)?terminated\s+due\s+to\s+error/i,
|
|
27
|
+
/capacity/i,
|
|
28
|
+
/temporarily\s+unavailable/i,
|
|
29
|
+
/timeout/i,
|
|
30
|
+
/stream\s+(ended|closed|terminated|interrupted)/i,
|
|
31
|
+
/empty\s+response/i,
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const ANTIGRAVITY_TRANSIENT_STATUSES = new Set([
|
|
35
|
+
HTTP_STATUS.SERVER_ERROR,
|
|
36
|
+
HTTP_STATUS.BAD_GATEWAY,
|
|
37
|
+
HTTP_STATUS.SERVICE_UNAVAILABLE,
|
|
38
|
+
HTTP_STATUS.GATEWAY_TIMEOUT,
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// Fields Google generateContent rejects (Claude/OpenAI/Qwen thinking fields set at body root by thinkingUnified.js)
|
|
42
|
+
const ANTIGRAVITY_REQUEST_BLACKLIST = [
|
|
43
|
+
"output_config",
|
|
44
|
+
"thinking",
|
|
45
|
+
"reasoning_effort",
|
|
46
|
+
"reasoning",
|
|
47
|
+
"enable_thinking",
|
|
48
|
+
"thinking_budget",
|
|
49
|
+
"thinkingConfig",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// Strip blacklisted fields from an object (used for both body.request and top-level body)
|
|
53
|
+
const stripBlacklisted = obj => {
|
|
54
|
+
for (const key of ANTIGRAVITY_REQUEST_BLACKLIST) delete obj[key];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Image generation model name patterns
|
|
58
|
+
const IMAGE_MODEL_PATTERNS = [
|
|
59
|
+
/image/i,
|
|
60
|
+
/imagen/i,
|
|
61
|
+
/image-generation/i,
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
// Detect if a model is an image generation model
|
|
65
|
+
function isImageModel(model) {
|
|
66
|
+
if (!model) return false;
|
|
67
|
+
return IMAGE_MODEL_PATTERNS.some(p => p.test(model));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Parse aspect ratio / resolution from model name suffixes
|
|
71
|
+
// e.g. "gemini-3.1-flash-image-16x9" -> { aspectRatio: "16:9" }
|
|
72
|
+
// e.g. "gemini-3.1-flash-image-1024x768" -> { aspectRatio: "4:3" }
|
|
73
|
+
function parseImageConfig(model) {
|
|
74
|
+
const config = { aspectRatio: "1:1" };
|
|
75
|
+
const resMatch = model.match(/(\d+)x(\d+)$/);
|
|
76
|
+
if (resMatch) {
|
|
77
|
+
const w = parseInt(resMatch[1]);
|
|
78
|
+
const h = parseInt(resMatch[2]);
|
|
79
|
+
if (w <= 16 && h <= 16) {
|
|
80
|
+
config.aspectRatio = `${w}:${h}`;
|
|
81
|
+
} else {
|
|
82
|
+
// Resolution like 1024x768 — derive aspect ratio
|
|
83
|
+
const gcd = (a, b) => b ? gcd(b, a % b) : a;
|
|
84
|
+
const d = gcd(w, h);
|
|
85
|
+
config.aspectRatio = `${w/d}:${h/d}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return config;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function uuidFromSeed(seed) {
|
|
92
|
+
const bytes = crypto.createHash("sha256").update(String(seed || "antigravity")).digest().subarray(0, 16);
|
|
93
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x50;
|
|
94
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
95
|
+
const hex = bytes.toString("hex");
|
|
96
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildIdeRequestId({ body, request, credentials, model, requestType }) {
|
|
100
|
+
if (ANTIGRAVITY_IDE_REQUEST_ID_RE.test(body?.requestId || "")) {
|
|
101
|
+
return body.requestId;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sessionId = request?.sessionId || body?.request?.sessionId || credentials?._clientSessionId || credentials?.connectionId || credentials?.email || "anonymous";
|
|
105
|
+
const conversationId = uuidFromSeed(`antigravity:conversation:${sessionId}`);
|
|
106
|
+
const trajectoryId = uuidFromSeed(`antigravity:trajectory:${sessionId}:${model}:${requestType}`);
|
|
107
|
+
const contentCount = Array.isArray(request?.contents) ? request.contents.length : 1;
|
|
108
|
+
const step = Math.max(1, contentCount * 2 - 1);
|
|
109
|
+
return `agent/${conversationId}/${Date.now()}/${trajectoryId}/${step}`;
|
|
110
|
+
}
|
|
22
111
|
|
|
23
112
|
export class AntigravityExecutor extends BaseExecutor {
|
|
24
113
|
constructor() {
|
|
@@ -28,108 +117,157 @@ export class AntigravityExecutor extends BaseExecutor {
|
|
|
28
117
|
buildUrl(model, stream, urlIndex = 0) {
|
|
29
118
|
const baseUrls = this.getBaseUrls();
|
|
30
119
|
const baseUrl = baseUrls[urlIndex] || baseUrls[0];
|
|
31
|
-
|
|
120
|
+
// Image generation MUST use non-streaming generateContent
|
|
121
|
+
const forceNonStream = isImageModel(model);
|
|
122
|
+
const action = (stream && !forceNonStream) ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
32
123
|
return `${baseUrl}/v1internal:${action}`;
|
|
33
124
|
}
|
|
34
125
|
|
|
126
|
+
// sessionId comes from transformRequest output; base.execute runs transformRequest before
|
|
127
|
+
// buildHeaders, so we read it from instance state cached there (fallback: explicit arg).
|
|
35
128
|
buildHeaders(credentials, stream = true, sessionId = null) {
|
|
36
129
|
return {
|
|
37
130
|
"Content-Type": "application/json",
|
|
38
131
|
"Authorization": `Bearer ${credentials.accessToken}`,
|
|
39
|
-
"User-Agent": this.config.headers?.["User-Agent"] ||
|
|
40
|
-
[INTERNAL_REQUEST_HEADER.name]: INTERNAL_REQUEST_HEADER.value,
|
|
41
|
-
...(sessionId && { "X-Machine-Session-Id": sessionId }),
|
|
42
|
-
"Accept": stream ? "text/event-stream" : "application/json"
|
|
132
|
+
"User-Agent": this.config.headers?.["User-Agent"] || ANTIGRAVITY_HEADERS["User-Agent"],
|
|
43
133
|
};
|
|
44
134
|
}
|
|
45
135
|
|
|
46
136
|
transformRequest(model, body, stream, credentials) {
|
|
47
|
-
const projectId = credentials?.projectId
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
137
|
+
const projectId = credentials?.projectId || this.generateProjectId();
|
|
138
|
+
|
|
139
|
+
// ─── Image generation: completely different request structure ───
|
|
140
|
+
if (isImageModel(model)) {
|
|
141
|
+
const imageConfig = parseImageConfig(model);
|
|
142
|
+
// Strip model name suffixes for the actual API model name
|
|
143
|
+
const cleanModel = model.replace(/-(\d+)x(\d+)$/, "");
|
|
144
|
+
|
|
145
|
+
// Build simplified contents — text-only, merge all user messages
|
|
146
|
+
const contents = [];
|
|
147
|
+
const srcContents = body.request?.contents || body.contents || [];
|
|
148
|
+
for (const c of srcContents) {
|
|
149
|
+
const textParts = (c.parts || []).filter(p => p.text !== undefined).map(p => ({ text: p.text }));
|
|
150
|
+
if (textParts.length > 0) {
|
|
151
|
+
contents.push({ role: c.role || "user", parts: textParts });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const sessionId = resolveSessionId({
|
|
156
|
+
headers: credentials?.rawHeaders,
|
|
157
|
+
body,
|
|
158
|
+
connectionId: credentials?.email || credentials?.connectionId,
|
|
159
|
+
scope: "antigravity",
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
this._lastSessionId = sessionId;
|
|
163
|
+
const request = {
|
|
164
|
+
contents,
|
|
165
|
+
generationConfig: {
|
|
166
|
+
temperature: 1.0,
|
|
167
|
+
topP: 0.95,
|
|
168
|
+
topK: 40,
|
|
169
|
+
maxOutputTokens: 8192,
|
|
170
|
+
imageConfig,
|
|
171
|
+
},
|
|
172
|
+
sessionId,
|
|
173
|
+
// No tools, no systemInstruction, no safetySettings for image gen
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
project: projectId,
|
|
178
|
+
model: cleanModel,
|
|
179
|
+
userAgent: "antigravity",
|
|
180
|
+
requestType: "image_gen",
|
|
181
|
+
requestId: buildIdeRequestId({ body, request, credentials, model: cleanModel, requestType: "image_gen" }),
|
|
182
|
+
request,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── Standard (non-image) request ───
|
|
187
|
+
// Fix contents for Claude models via Antigravity
|
|
188
|
+
const contents = body.request?.contents?.map(c => {
|
|
68
189
|
let role = c.role;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
190
|
+
// functionResponse must be role "user" for Claude models
|
|
191
|
+
if (c.parts?.some(p => p.functionResponse)) {
|
|
192
|
+
role = "user";
|
|
193
|
+
}
|
|
194
|
+
// Strip thought-only parts, keep thoughtSignature on functionCall parts (Gemini 3+ requires it)
|
|
195
|
+
const parts = c.parts?.filter(p => {
|
|
72
196
|
if (p.thought && !p.functionCall) return false;
|
|
73
197
|
if (p.thoughtSignature && !p.functionCall && !p.text) return false;
|
|
74
198
|
return true;
|
|
75
199
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
200
|
+
// Gemini 3+ rejects functionCall parts without thoughtSignature. Clients (Claude Code, IDE)
|
|
201
|
+
// don't persist thoughtSignature in their history, so backfill the default signature on any
|
|
202
|
+
// functionCall part that arrives without one.
|
|
203
|
+
const needsBackfill = parts?.some(p => p.functionCall && !p.thoughtSignature) ?? false;
|
|
204
|
+
if (role !== c.role || parts?.length !== c.parts?.length || needsBackfill) {
|
|
205
|
+
return {
|
|
206
|
+
...c, role,
|
|
207
|
+
parts: needsBackfill
|
|
208
|
+
? parts.map(p => (p.functionCall && !p.thoughtSignature)
|
|
209
|
+
? { ...p, thoughtSignature: DEFAULT_THINKING_AG_SIGNATURE }
|
|
210
|
+
: p)
|
|
211
|
+
: parts,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return c;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Sanitize tool schemas and function names before sending to Antigravity.
|
|
218
|
+
let tools = body.request?.tools;
|
|
219
|
+
|
|
220
|
+
if (tools && tools.length > 0) {
|
|
221
|
+
// Merge all groups into a single functionDeclarations group (Gemini expects 1 group)
|
|
222
|
+
const seenToolNames = new Set();
|
|
223
|
+
const allDeclarations = [];
|
|
224
|
+
for (const group of tools) {
|
|
225
|
+
for (const fn of group.functionDeclarations || []) {
|
|
226
|
+
const name = sanitizeFunctionName(fn.name);
|
|
227
|
+
if (seenToolNames.has(name)) continue;
|
|
228
|
+
seenToolNames.add(name);
|
|
229
|
+
allDeclarations.push({
|
|
230
|
+
...fn,
|
|
231
|
+
name,
|
|
232
|
+
parameters: fn.parameters
|
|
233
|
+
? cleanJSONSchemaForAntigravity(structuredClone(fn.parameters))
|
|
234
|
+
: { type: "object", properties: { reason: { type: "string", description: "Brief explanation" } }, required: ["reason"] }
|
|
235
|
+
});
|
|
90
236
|
}
|
|
91
237
|
}
|
|
238
|
+
tools = allDeclarations.length > 0 ? [{ functionDeclarations: allDeclarations }] : [];
|
|
92
239
|
}
|
|
93
240
|
|
|
94
|
-
|
|
241
|
+
// Strip tools/toolConfig (handled separately) and blacklisted fields that Google rejects
|
|
242
|
+
const { tools: _originalTools, toolConfig: _originalToolConfig, ...requestWithoutTools } = body.request || {};
|
|
243
|
+
stripBlacklisted(requestWithoutTools);
|
|
244
|
+
const generationConfig = { ...(requestWithoutTools.generationConfig || {}) };
|
|
95
245
|
if (generationConfig.maxOutputTokens > MAX_ANTIGRAVITY_OUTPUT_TOKENS) {
|
|
96
246
|
generationConfig.maxOutputTokens = MAX_ANTIGRAVITY_OUTPUT_TOKENS;
|
|
97
247
|
}
|
|
98
248
|
|
|
99
|
-
// Ensure conversation ends with a user message (required by Claude/Antigravity)
|
|
100
|
-
while (finalContents.length > 0 && finalContents[finalContents.length - 1].role === "model") {
|
|
101
|
-
finalContents.pop();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Sanitize tools
|
|
105
|
-
let tools = req.tools;
|
|
106
|
-
if (tools && tools.length > 0) {
|
|
107
|
-
const allDeclarations = tools.flatMap(group =>
|
|
108
|
-
(group.functionDeclarations || []).map(fn => ({
|
|
109
|
-
...fn,
|
|
110
|
-
name: sanitizeFunctionName(fn.name),
|
|
111
|
-
parameters: fn.parameters
|
|
112
|
-
? cleanJSONSchemaForAntigravity(structuredClone(fn.parameters))
|
|
113
|
-
: { type: "object", properties: { reason: { type: "string", description: "Brief explanation" } }, required: ["reason"] }
|
|
114
|
-
}))
|
|
115
|
-
);
|
|
116
|
-
tools = allDeclarations.length > 0 ? [{ functionDeclarations: allDeclarations }] : [];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
249
|
const transformedRequest = {
|
|
120
|
-
|
|
250
|
+
...requestWithoutTools,
|
|
121
251
|
generationConfig,
|
|
122
|
-
|
|
123
|
-
...(tools
|
|
252
|
+
...(contents && { contents }),
|
|
253
|
+
...(tools && { tools }),
|
|
254
|
+
sessionId: body.request?.sessionId || resolveSessionId({ headers: credentials?.rawHeaders, body, connectionId: credentials?.email || credentials?.connectionId, scope: "antigravity" }),
|
|
255
|
+
safetySettings: undefined,
|
|
124
256
|
...(tools?.length > 0 && { toolConfig: { functionCallingConfig: { mode: "VALIDATED" } } })
|
|
125
257
|
};
|
|
126
258
|
|
|
259
|
+
// Strip blacklisted thinking fields from top-level body (set by thinkingUnified.js at root, not body.request)
|
|
260
|
+
stripBlacklisted(body);
|
|
261
|
+
|
|
262
|
+
this._lastSessionId = transformedRequest.sessionId; // cached for buildHeaders (base.execute order)
|
|
263
|
+
|
|
127
264
|
return {
|
|
128
|
-
|
|
265
|
+
...body,
|
|
266
|
+
project: projectId,
|
|
129
267
|
model: model,
|
|
130
|
-
userAgent:
|
|
131
|
-
requestType:
|
|
132
|
-
requestId: body
|
|
268
|
+
userAgent: "antigravity",
|
|
269
|
+
requestType: "agent",
|
|
270
|
+
requestId: buildIdeRequestId({ body, request: transformedRequest, credentials, model, requestType: "agent" }),
|
|
133
271
|
request: transformedRequest
|
|
134
272
|
};
|
|
135
273
|
}
|
|
@@ -223,101 +361,49 @@ export class AntigravityExecutor extends BaseExecutor {
|
|
|
223
361
|
return totalMs > 0 ? totalMs : null;
|
|
224
362
|
}
|
|
225
363
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const messageStr = typeof message === "string" ? message : JSON.stringify(message);
|
|
234
|
-
const retryMs = this.parseRetryFromErrorMessage(messageStr);
|
|
235
|
-
|
|
236
|
-
if (retryMs) {
|
|
237
|
-
return {
|
|
238
|
-
status: HTTP_STATUS.RATE_LIMITED, // Convert 403 quota resets to 429
|
|
239
|
-
message: messageStr,
|
|
240
|
-
resetsAtMs: Date.now() + retryMs
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return { status: response.status, message: messageStr };
|
|
364
|
+
extractErrorMessage(errorJson, bodyText = "") {
|
|
365
|
+
return [
|
|
366
|
+
errorJson?.error?.message,
|
|
367
|
+
errorJson?.message,
|
|
368
|
+
errorJson?.error,
|
|
369
|
+
bodyText,
|
|
370
|
+
].filter(Boolean).map(v => typeof v === "string" ? v : JSON.stringify(v)).join("\n");
|
|
245
371
|
}
|
|
246
372
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const fallbackCount = this.getFallbackCount();
|
|
254
|
-
let lastError = null;
|
|
255
|
-
let lastStatus = 0;
|
|
256
|
-
const MAX_AUTO_RETRIES = 3;
|
|
257
|
-
const MAX_RETRY_AFTER_RETRIES = 3;
|
|
258
|
-
const retryAttemptsByUrl = {}; // Track retry attempts per URL
|
|
259
|
-
const retryAfterAttemptsByUrl = {}; // Track Retry-After retries per URL
|
|
260
|
-
|
|
261
|
-
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
|
|
262
|
-
const url = this.buildUrl(model, stream, urlIndex);
|
|
263
|
-
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
|
264
|
-
const sessionId = transformedBody.request?.sessionId;
|
|
265
|
-
const headers = this.buildHeaders(credentials, stream, sessionId);
|
|
266
|
-
|
|
267
|
-
// Initialize retry counters for this URL
|
|
268
|
-
if (!retryAttemptsByUrl[urlIndex]) {
|
|
269
|
-
retryAttemptsByUrl[urlIndex] = 0;
|
|
270
|
-
}
|
|
271
|
-
if (!retryAfterAttemptsByUrl[urlIndex]) {
|
|
272
|
-
retryAfterAttemptsByUrl[urlIndex] = 0;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
const response = await proxyAwareFetch(url, {
|
|
277
|
-
method: "POST",
|
|
278
|
-
headers,
|
|
279
|
-
body: JSON.stringify(transformedBody),
|
|
280
|
-
signal
|
|
281
|
-
}, proxyOptions);
|
|
282
|
-
|
|
283
|
-
const isForbiddenQuota = response.status === HTTP_STATUS.FORBIDDEN;
|
|
284
|
-
const isRateLimited = response.status === HTTP_STATUS.RATE_LIMITED;
|
|
373
|
+
isTransientAntigravityError(status, message) {
|
|
374
|
+
if (status === HTTP_STATUS.RATE_LIMITED) return true;
|
|
375
|
+
if (ANTIGRAVITY_TRANSIENT_STATUSES.has(status)) return true;
|
|
376
|
+
return ANTIGRAVITY_TRANSIENT_ERROR_PATTERNS.some(pattern => pattern.test(message || ""));
|
|
377
|
+
}
|
|
285
378
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
379
|
+
// Hook called by BaseExecutor.tryRetry: derive delay from Retry-After (header → body),
|
|
380
|
+
// cap at MAX_RETRY_AFTER_MS, else retry transient Antigravity failures with backoff.
|
|
381
|
+
// Return false to veto (fallback URL / final error).
|
|
382
|
+
async computeRetryDelay(response, attempt) {
|
|
383
|
+
let bodyText = "";
|
|
384
|
+
let errorJson = null;
|
|
385
|
+
let retryMs = this.parseRetryHeaders(response.headers);
|
|
290
386
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
await new Promise(r => setTimeout(r, delay));
|
|
298
|
-
urlIndex--; // Retry same URL
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
387
|
+
try {
|
|
388
|
+
bodyText = await response.clone().text();
|
|
389
|
+
errorJson = bodyText ? JSON.parse(bodyText) : null;
|
|
390
|
+
} catch {
|
|
391
|
+
// ignore parse errors → fall through to status/message based retry
|
|
392
|
+
}
|
|
302
393
|
|
|
303
|
-
|
|
304
|
-
lastStatus = response.status;
|
|
305
|
-
lastError = await response.text();
|
|
306
|
-
if (urlIndex + 1 < fallbackCount) continue;
|
|
307
|
-
}
|
|
394
|
+
const errorMessage = this.extractErrorMessage(errorJson, bodyText);
|
|
308
395
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
lastError = error;
|
|
312
|
-
if (urlIndex + 1 < fallbackCount) {
|
|
313
|
-
log?.debug?.("RETRY", `Error on ${url}, trying fallback ${urlIndex + 1}`);
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
throw error;
|
|
317
|
-
}
|
|
396
|
+
if (!retryMs) {
|
|
397
|
+
retryMs = this.parseRetryFromErrorMessage(errorMessage);
|
|
318
398
|
}
|
|
399
|
+
if (retryMs) return retryMs <= MAX_RETRY_AFTER_MS ? retryMs : false;
|
|
400
|
+
|
|
401
|
+
if (!this.isTransientAntigravityError(response.status, errorMessage)) return false;
|
|
319
402
|
|
|
320
|
-
|
|
403
|
+
const cap = response.status === HTTP_STATUS.RATE_LIMITED
|
|
404
|
+
? MAX_RETRY_AFTER_MS
|
|
405
|
+
: ANTIGRAVITY_TRANSIENT_RETRY_MAX_MS;
|
|
406
|
+
return Math.min(1000 * (2 ** attempt), cap); // exponential backoff
|
|
321
407
|
}
|
|
322
408
|
|
|
323
409
|
/**
|
|
@@ -2,6 +2,7 @@ import { HTTP_STATUS, RETRY_CONFIG, DEFAULT_RETRY_CONFIG, resolveRetryEntry, FET
|
|
|
2
2
|
import { shouldRefreshCredentials } from "../services/oauthCredentialManager.js";
|
|
3
3
|
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
|
4
4
|
import { dbg } from "../utils/debugLog.js";
|
|
5
|
+
import { ANTHROPIC_API_VERSION, OPENAI_COMPAT_BASE, ANTHROPIC_COMPAT_BASE } from "../providers/shared.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* BaseExecutor - Base class for provider executors
|
|
@@ -27,13 +28,13 @@ export class BaseExecutor {
|
|
|
27
28
|
|
|
28
29
|
buildUrl(model, stream, urlIndex = 0, credentials = null) {
|
|
29
30
|
if (this.provider?.startsWith?.("openai-compatible-")) {
|
|
30
|
-
const baseUrl = credentials?.providerSpecificData?.baseUrl ||
|
|
31
|
+
const baseUrl = credentials?.providerSpecificData?.baseUrl || OPENAI_COMPAT_BASE;
|
|
31
32
|
const normalized = baseUrl.replace(/\/$/, "");
|
|
32
33
|
const path = this.provider.includes("responses") ? "/responses" : "/chat/completions";
|
|
33
34
|
return `${normalized}${path}`;
|
|
34
35
|
}
|
|
35
36
|
if (this.provider?.startsWith?.("anthropic-compatible-")) {
|
|
36
|
-
const baseUrl = credentials?.providerSpecificData?.baseUrl ||
|
|
37
|
+
const baseUrl = credentials?.providerSpecificData?.baseUrl || ANTHROPIC_COMPAT_BASE;
|
|
37
38
|
const normalized = baseUrl.replace(/\/$/, "");
|
|
38
39
|
return `${normalized}/messages`;
|
|
39
40
|
}
|
|
@@ -55,7 +56,7 @@ export class BaseExecutor {
|
|
|
55
56
|
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
|
56
57
|
}
|
|
57
58
|
if (!headers["anthropic-version"]) {
|
|
58
|
-
headers["anthropic-version"] =
|
|
59
|
+
headers["anthropic-version"] = ANTHROPIC_API_VERSION;
|
|
59
60
|
}
|
|
60
61
|
} else {
|
|
61
62
|
// Standard Bearer token auth for other providers
|
|
@@ -105,16 +106,20 @@ export class BaseExecutor {
|
|
|
105
106
|
const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...this.config.retry };
|
|
106
107
|
|
|
107
108
|
// Schedule retry via retryConfig[statusKey]. Returns true when caller should `urlIndex--; continue`
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Allow fallback to next URL or account rotation instead
|
|
111
|
-
if (statusKey === HTTP_STATUS.RATE_LIMITED || statusKey === HTTP_STATUS.FORBIDDEN) return false;
|
|
112
|
-
|
|
109
|
+
// response (optional) lets a subclass hook compute a dynamic delay (e.g. antigravity Retry-After).
|
|
110
|
+
const tryRetry = async (urlIndex, statusKey, reason, response = null) => {
|
|
113
111
|
const { attempts, delayMs } = resolveRetryEntry(retryConfig[statusKey]);
|
|
114
112
|
if (attempts <= 0 || retryAttemptsByUrl[urlIndex] >= attempts) return false;
|
|
113
|
+
// Hook: subclass may derive delay from the response (headers/body). null → skip retry, use fallback.
|
|
114
|
+
let waitMs = delayMs;
|
|
115
|
+
if (response && this.computeRetryDelay) {
|
|
116
|
+
const dynamic = await this.computeRetryDelay(response, retryAttemptsByUrl[urlIndex] + 1, delayMs);
|
|
117
|
+
if (dynamic === false) return false; // hook vetoes retry (e.g. Retry-After too long)
|
|
118
|
+
if (dynamic != null) waitMs = dynamic;
|
|
119
|
+
}
|
|
115
120
|
retryAttemptsByUrl[urlIndex]++;
|
|
116
|
-
log?.debug?.("RETRY", `${reason} retry ${retryAttemptsByUrl[urlIndex]}/${attempts} after ${
|
|
117
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
121
|
+
log?.debug?.("RETRY", `${reason} retry ${retryAttemptsByUrl[urlIndex]}/${attempts} after ${waitMs / 1000}s`);
|
|
122
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
118
123
|
return true;
|
|
119
124
|
};
|
|
120
125
|
|
|
@@ -146,7 +151,7 @@ export class BaseExecutor {
|
|
|
146
151
|
const cl = response.headers?.get?.("content-length") || "?";
|
|
147
152
|
dbg("FETCH", `${this.provider.toUpperCase()} ← ${response.status} | ttft=${Date.now() - fetchT0}ms | ct=${ct} | cl=${cl}`);
|
|
148
153
|
|
|
149
|
-
if (await tryRetry(urlIndex, response.status, `status ${response.status}
|
|
154
|
+
if (await tryRetry(urlIndex, response.status, `status ${response.status}`, response)) { urlIndex--; continue; }
|
|
150
155
|
|
|
151
156
|
if (this.shouldRetry(response.status, urlIndex)) {
|
|
152
157
|
log?.debug?.("RETRY", `${response.status} on ${url}, trying fallback ${urlIndex + 1}`);
|