@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.
Files changed (314) hide show
  1. package/dist/lib/headroom/detect.js +97 -7
  2. package/dist/lib/headroom/process.js +136 -4
  3. package/dist/open-sse/config/appConstants.js +23 -79
  4. package/dist/open-sse/config/cliFingerprints.js +1 -1
  5. package/dist/open-sse/config/errorConfig.js +0 -11
  6. package/dist/open-sse/config/grokCli.js +10 -0
  7. package/dist/open-sse/config/kiroConstants.js +99 -46
  8. package/dist/open-sse/config/runtimeConfig.js +2 -0
  9. package/dist/open-sse/config/ttsModels.js +2 -0
  10. package/dist/open-sse/executors/antigravity.js +250 -164
  11. package/dist/open-sse/executors/base.js +16 -11
  12. package/dist/open-sse/executors/codex.js +269 -1108
  13. package/dist/open-sse/executors/commandCode.js +1 -1
  14. package/dist/open-sse/executors/commandcode.js +10 -4
  15. package/dist/open-sse/executors/cursor.js +96 -203
  16. package/dist/open-sse/executors/default.js +146 -244
  17. package/dist/open-sse/executors/github.js +128 -73
  18. package/dist/open-sse/executors/grok-cli.js +527 -94
  19. package/dist/open-sse/executors/grok-web.js +5 -7
  20. package/dist/open-sse/executors/index.js +12 -0
  21. package/dist/open-sse/executors/kiro.js +150 -61
  22. package/dist/open-sse/executors/opencode-go.js +12 -4
  23. package/dist/open-sse/executors/opencode.js +1 -1
  24. package/dist/open-sse/executors/perplexity-web.js +20 -96
  25. package/dist/open-sse/executors/qoder.js +28 -18
  26. package/dist/open-sse/executors/vertex.js +53 -7
  27. package/dist/open-sse/executors/xiaomi-tokenplan.js +6 -5
  28. package/dist/open-sse/handlers/chatCore/nonStreamingHandler.js +97 -85
  29. package/dist/open-sse/handlers/chatCore/requestDetail.js +32 -8
  30. package/dist/open-sse/handlers/chatCore/sseToJsonHandler.js +13 -7
  31. package/dist/open-sse/handlers/chatCore/streamingHandler.js +74 -25
  32. package/dist/open-sse/handlers/chatCore.js +35 -41
  33. package/dist/open-sse/handlers/embeddingProviders/index.js +1 -0
  34. package/dist/open-sse/handlers/embeddingProviders/openai.js +8 -16
  35. package/dist/open-sse/handlers/imageProviders/blackForestLabs.js +2 -21
  36. package/dist/open-sse/handlers/imageProviders/cloudflareAi.js +2 -30
  37. package/dist/open-sse/handlers/imageProviders/codex.js +2 -20
  38. package/dist/open-sse/handlers/imageProviders/comfyui.js +5 -18
  39. package/dist/open-sse/handlers/imageProviders/falAi.js +2 -22
  40. package/dist/open-sse/handlers/imageProviders/gemini.js +2 -16
  41. package/dist/open-sse/handlers/imageProviders/huggingface.js +2 -17
  42. package/dist/open-sse/handlers/imageProviders/index.js +3 -4
  43. package/dist/open-sse/handlers/imageProviders/leonardo.js +1 -1
  44. package/dist/open-sse/handlers/imageProviders/nanobanana.js +4 -23
  45. package/dist/open-sse/handlers/imageProviders/openai.js +15 -43
  46. package/dist/open-sse/handlers/imageProviders/runwayml.js +2 -26
  47. package/dist/open-sse/handlers/imageProviders/sdwebui.js +4 -20
  48. package/dist/open-sse/handlers/imageProviders/stabilityAi.js +2 -26
  49. package/dist/open-sse/handlers/imageProviders/weavy.js +1 -1
  50. package/dist/open-sse/handlers/search/chatSearch.js +60 -14
  51. package/dist/open-sse/handlers/ttsProviders/gemini.js +14 -3
  52. package/dist/open-sse/handlers/ttsProviders/index.js +4 -2
  53. package/dist/open-sse/handlers/ttsProviders/openai.js +4 -1
  54. package/dist/open-sse/handlers/ttsProviders/openrouter.js +7 -4
  55. package/dist/open-sse/handlers/videoCore.js +166 -0
  56. package/dist/open-sse/handlers/videoProviders/weavy.js +1 -1
  57. package/dist/open-sse/mcp-server/catalog.js +1 -1
  58. package/dist/open-sse/mcp-server/server.js +2 -2
  59. package/dist/open-sse/mcp-server/tools/advancedTools.js +2 -2
  60. package/dist/open-sse/mcp-server/tools/agentSkillTools.js +1 -1
  61. package/dist/open-sse/mcp-server/tools/compressionTools.js +4 -4
  62. package/dist/open-sse/mcp-server/tools/gamificationTools.js +10 -10
  63. package/dist/open-sse/mcp-server/tools/githubSkillTools.js +1 -1
  64. package/dist/open-sse/mcp-server/tools/memoryTools.js +3 -3
  65. package/dist/open-sse/mcp-server/tools/notionTools.js +2 -2
  66. package/dist/open-sse/mcp-server/tools/obsidianTools.js +2 -2
  67. package/dist/open-sse/mcp-server/tools/pickFastestModel.js +1 -1
  68. package/dist/open-sse/mcp-server/tools/pluginTools.js +4 -4
  69. package/dist/open-sse/mcp-server/tools/skillTools.js +2 -2
  70. package/dist/open-sse/providers/capabilities.js +24 -4
  71. package/dist/open-sse/providers/registry/alicode-intl.js +1 -1
  72. package/dist/open-sse/providers/registry/anthropic.js +1 -8
  73. package/dist/open-sse/providers/registry/codex.js +0 -9
  74. package/dist/open-sse/providers/registry/cohere.js +0 -2
  75. package/dist/open-sse/providers/registry/gemini.js +0 -2
  76. package/dist/open-sse/providers/registry/github.js +9 -6
  77. package/dist/open-sse/providers/registry/grok-cli.js +28 -18
  78. package/dist/open-sse/providers/registry/index.js +74 -80
  79. package/dist/open-sse/providers/registry/kiro.js +12 -0
  80. package/dist/open-sse/providers/registry/mistral.js +0 -3
  81. package/dist/open-sse/providers/registry/openai.js +0 -3
  82. package/dist/open-sse/providers/registry/perplexity.js +0 -2
  83. package/dist/open-sse/providers/registry/xai.js +5 -3
  84. package/dist/open-sse/rtk/autodetect.js +10 -2
  85. package/dist/open-sse/rtk/caveman.js +2 -93
  86. package/dist/open-sse/rtk/cavemanPrompts.js +34 -2
  87. package/dist/open-sse/rtk/constants.js +7 -2
  88. package/dist/open-sse/rtk/headroom.js +133 -0
  89. package/dist/open-sse/rtk/ponytail.js +1 -1
  90. package/dist/open-sse/rtk/registry.js +2 -0
  91. package/dist/open-sse/services/accountFallback.js +0 -108
  92. package/dist/open-sse/services/combo/quotaExhaustionCutoff.js +2 -2
  93. package/dist/open-sse/services/combo/quotaShareStrategy.js +1 -1
  94. package/dist/open-sse/services/combo/resolveAutoStrategy.js +1 -1
  95. package/dist/open-sse/services/combo/sessionStickiness.js +1 -1
  96. package/dist/open-sse/services/combo/targetSorters.js +2 -2
  97. package/dist/open-sse/services/combo/validateQuality.js +1 -1
  98. package/dist/open-sse/services/comboConfig.js +1 -1
  99. package/dist/open-sse/services/emergencyFallback.js +1 -1
  100. package/dist/open-sse/services/grokCliModels.js +127 -0
  101. package/dist/open-sse/services/model.js +7 -1
  102. package/dist/open-sse/services/provider.js +1 -1
  103. package/dist/open-sse/services/proxyAutoSelector.js +1 -1
  104. package/dist/open-sse/services/tokenRefresh/providers.js +3 -3
  105. package/dist/open-sse/services/usage/codex.js +148 -214
  106. package/dist/open-sse/services/usage/grok-cli.js +61 -7
  107. package/dist/open-sse/services/usage/misc.js +0 -19
  108. package/dist/open-sse/services/usage.js +0 -2
  109. package/dist/open-sse/translator/concerns/paramSupport.js +2 -2
  110. package/dist/open-sse/translator/concerns/thinkingUnified.js +6 -0
  111. package/dist/open-sse/translator/index.js +97 -59
  112. package/dist/open-sse/translator/request/claude-to-kiro.js +67 -40
  113. package/dist/open-sse/translator/request/openai-responses.js +1 -0
  114. package/dist/open-sse/translator/request/openai-to-commandcode.js +0 -4
  115. package/dist/open-sse/translator/request/openai-to-kiro.js +49 -20
  116. package/dist/open-sse/translator/response/commandcode-to-openai.js +0 -4
  117. package/dist/open-sse/utils/bypassHandler.js +18 -3
  118. package/dist/open-sse/utils/claudeCloaking.js +15 -5
  119. package/dist/open-sse/utils/cursorImages.js +1 -1
  120. package/dist/open-sse/utils/error.js +0 -17
  121. package/dist/open-sse/utils/keepaliveThreshold.js +4 -4
  122. package/dist/open-sse/utils/kiroSessionReplay.js +125 -0
  123. package/dist/open-sse/utils/noThinkingAlias.js +1 -1
  124. package/dist/open-sse/utils/proxyFetch.js +0 -1
  125. package/dist/open-sse/utils/reasoningContentInjector.js +4 -7
  126. package/dist/open-sse/utils/responsesStatePolicy.js +1 -1
  127. package/dist/open-sse/utils/responsesStreamHelpers.js +1 -0
  128. package/dist/open-sse/utils/sessionManager.js +49 -22
  129. package/dist/open-sse/utils/stream.js +54 -77
  130. package/dist/open-sse/utils/streamHandler.js +19 -15
  131. package/dist/open-sse/utils/streamHelpers.js +2 -17
  132. package/dist/open-sse/utils/usageTracking.js +103 -12
  133. package/dist/routes/headroom/extras/route.js +58 -0
  134. package/dist/routes/headroom/restart/route.js +33 -0
  135. package/open-sse/config/appConstants.js +23 -79
  136. package/open-sse/config/errorConfig.js +0 -11
  137. package/open-sse/config/grokCli.js +10 -0
  138. package/open-sse/config/kiroConstants.js +99 -46
  139. package/open-sse/config/runtimeConfig.js +2 -0
  140. package/open-sse/config/ttsModels.js +2 -0
  141. package/open-sse/executors/antigravity.js +250 -164
  142. package/open-sse/executors/base.js +16 -11
  143. package/open-sse/executors/codex.js +269 -1108
  144. package/open-sse/executors/commandcode.js +10 -4
  145. package/open-sse/executors/cursor.js +96 -203
  146. package/open-sse/executors/default.js +146 -244
  147. package/open-sse/executors/github.js +128 -73
  148. package/open-sse/executors/grok-cli.js +527 -94
  149. package/open-sse/executors/grok-web.js +5 -7
  150. package/open-sse/executors/index.js +12 -0
  151. package/open-sse/executors/kiro.js +150 -61
  152. package/open-sse/executors/opencode-go.js +12 -4
  153. package/open-sse/executors/opencode.js +1 -1
  154. package/open-sse/executors/perplexity-web.js +20 -96
  155. package/open-sse/executors/qoder.js +28 -18
  156. package/open-sse/executors/vertex.js +53 -7
  157. package/open-sse/executors/xiaomi-tokenplan.js +6 -5
  158. package/open-sse/handlers/chatCore/nonStreamingHandler.js +97 -85
  159. package/open-sse/handlers/chatCore/requestDetail.js +32 -8
  160. package/open-sse/handlers/chatCore/sseToJsonHandler.js +13 -7
  161. package/open-sse/handlers/chatCore/streamingHandler.js +74 -25
  162. package/open-sse/handlers/chatCore.js +35 -41
  163. package/open-sse/handlers/embeddingProviders/index.js +1 -0
  164. package/open-sse/handlers/embeddingProviders/openai.js +8 -16
  165. package/open-sse/handlers/imageProviders/blackForestLabs.js +2 -21
  166. package/open-sse/handlers/imageProviders/cloudflareAi.js +2 -30
  167. package/open-sse/handlers/imageProviders/codex.js +2 -20
  168. package/open-sse/handlers/imageProviders/comfyui.js +5 -18
  169. package/open-sse/handlers/imageProviders/falAi.js +2 -22
  170. package/open-sse/handlers/imageProviders/gemini.js +2 -16
  171. package/open-sse/handlers/imageProviders/huggingface.js +2 -17
  172. package/open-sse/handlers/imageProviders/index.js +3 -4
  173. package/open-sse/handlers/imageProviders/leonardo.js +1 -1
  174. package/open-sse/handlers/imageProviders/nanobanana.js +4 -23
  175. package/open-sse/handlers/imageProviders/openai.js +15 -43
  176. package/open-sse/handlers/imageProviders/runwayml.js +2 -26
  177. package/open-sse/handlers/imageProviders/sdwebui.js +4 -20
  178. package/open-sse/handlers/imageProviders/stabilityAi.js +2 -26
  179. package/open-sse/handlers/imageProviders/weavy.js +1 -1
  180. package/open-sse/handlers/search/chatSearch.js +60 -14
  181. package/open-sse/handlers/ttsProviders/gemini.js +14 -3
  182. package/open-sse/handlers/ttsProviders/index.js +4 -2
  183. package/open-sse/handlers/ttsProviders/openai.js +4 -1
  184. package/open-sse/handlers/ttsProviders/openrouter.js +7 -4
  185. package/open-sse/handlers/videoCore.js +166 -0
  186. package/open-sse/handlers/videoProviders/weavy.js +1 -1
  187. package/open-sse/mcp-server/audit.js +1 -1
  188. package/open-sse/mcp-server/catalog.js +2 -2
  189. package/open-sse/mcp-server/descriptionCompressor.js +1 -1
  190. package/open-sse/mcp-server/mcpCallerIdentity.js +1 -1
  191. package/open-sse/mcp-server/schemas/tools.js +1 -1
  192. package/open-sse/mcp-server/server.js +4 -4
  193. package/open-sse/mcp-server/tools/advancedTools.js +5 -5
  194. package/open-sse/mcp-server/tools/agentSkillTools.js +1 -1
  195. package/open-sse/mcp-server/tools/compressionTools.js +4 -4
  196. package/open-sse/mcp-server/tools/gamificationTools.js +10 -10
  197. package/open-sse/mcp-server/tools/githubSkillTools.js +1 -1
  198. package/open-sse/mcp-server/tools/memoryTools.js +3 -3
  199. package/open-sse/mcp-server/tools/notionTools.js +2 -2
  200. package/open-sse/mcp-server/tools/obsidianTools.js +2 -2
  201. package/open-sse/mcp-server/tools/pickFastestModel.js +3 -3
  202. package/open-sse/mcp-server/tools/pluginTools.js +4 -4
  203. package/open-sse/mcp-server/tools/skillTools.js +2 -2
  204. package/open-sse/providers/capabilities.js +24 -4
  205. package/open-sse/providers/registry/alicode-intl.js +1 -1
  206. package/open-sse/providers/registry/anthropic.js +1 -8
  207. package/open-sse/providers/registry/codex.js +0 -9
  208. package/open-sse/providers/registry/cohere.js +0 -2
  209. package/open-sse/providers/registry/gemini.js +0 -2
  210. package/open-sse/providers/registry/github.js +9 -6
  211. package/open-sse/providers/registry/grok-cli.js +28 -18
  212. package/open-sse/providers/registry/index.js +74 -80
  213. package/open-sse/providers/registry/kiro.js +12 -0
  214. package/open-sse/providers/registry/mistral.js +0 -3
  215. package/open-sse/providers/registry/openai.js +0 -3
  216. package/open-sse/providers/registry/perplexity.js +0 -2
  217. package/open-sse/providers/registry/xai.js +5 -3
  218. package/open-sse/rtk/autodetect.js +10 -2
  219. package/open-sse/rtk/caveman.js +2 -93
  220. package/open-sse/rtk/cavemanPrompts.js +34 -2
  221. package/open-sse/rtk/constants.js +7 -2
  222. package/open-sse/rtk/headroom.js +133 -0
  223. package/open-sse/rtk/ponytail.js +1 -1
  224. package/open-sse/rtk/registry.js +2 -0
  225. package/open-sse/services/accountFallback.js +0 -108
  226. package/open-sse/services/combo/quotaExhaustionCutoff.js +2 -2
  227. package/open-sse/services/combo/quotaShareStrategy.js +1 -1
  228. package/open-sse/services/combo/resolveAutoStrategy.js +1 -1
  229. package/open-sse/services/combo/sessionStickiness.js +1 -1
  230. package/open-sse/services/combo/shadowRouting.js +1 -1
  231. package/open-sse/services/combo/targetSorters.js +4 -4
  232. package/open-sse/services/combo/validateQuality.js +1 -1
  233. package/open-sse/services/evalRouting.js +1 -1
  234. package/open-sse/services/grokCliModels.js +127 -0
  235. package/open-sse/services/model.js +7 -1
  236. package/open-sse/services/modelCapabilities.js +1 -1
  237. package/open-sse/services/modelFamilyFallback.js +1 -1
  238. package/open-sse/services/provider.js +1 -1
  239. package/open-sse/services/providerCooldownTracker.js +1 -1
  240. package/open-sse/services/rateLimitManager.js +1 -1
  241. package/open-sse/services/tokenRefresh/providers.js +3 -3
  242. package/open-sse/services/usage/codex.js +148 -214
  243. package/open-sse/services/usage/grok-cli.js +61 -7
  244. package/open-sse/services/usage/misc.js +0 -19
  245. package/open-sse/services/usage.js +0 -2
  246. package/open-sse/translator/concerns/paramSupport.js +2 -2
  247. package/open-sse/translator/concerns/thinkingUnified.js +6 -0
  248. package/open-sse/translator/index.js +97 -59
  249. package/open-sse/translator/request/claude-to-kiro.js +67 -40
  250. package/open-sse/translator/request/openai-responses.js +1 -0
  251. package/open-sse/translator/request/openai-to-commandcode.js +0 -4
  252. package/open-sse/translator/request/openai-to-kiro.js +49 -20
  253. package/open-sse/translator/response/commandcode-to-openai.js +0 -4
  254. package/open-sse/utils/bypassHandler.js +18 -3
  255. package/open-sse/utils/claudeCloaking.js +15 -5
  256. package/open-sse/utils/error.js +0 -17
  257. package/open-sse/utils/kiroSessionReplay.js +125 -0
  258. package/open-sse/utils/logger.js +1 -1
  259. package/open-sse/utils/proxyFetch.js +0 -1
  260. package/open-sse/utils/reasoningContentInjector.js +4 -7
  261. package/open-sse/utils/responsesStreamHelpers.js +1 -0
  262. package/open-sse/utils/sessionManager.js +49 -22
  263. package/open-sse/utils/stream.js +54 -77
  264. package/open-sse/utils/streamHandler.js +19 -15
  265. package/open-sse/utils/streamHelpers.js +2 -17
  266. package/open-sse/utils/usageTracking.js +103 -12
  267. package/package.json +4 -7
  268. package/public/assets/EndpointPageClient.C2WMTYy0.js +6 -0
  269. package/public/assets/{Loading.Bj-WGl4k.js → Loading.BHPtQ9vj.js} +1 -1
  270. package/public/assets/{NoAuthProxyCard.qAv57b-0.js → NoAuthProxyCard.BDcXaWvM.js} +1 -1
  271. package/public/assets/index.Be3gbrBl.js +89 -0
  272. package/public/assets/index.COMgQZYj.css +1 -0
  273. package/public/assets/{page.CbkENBd5.js → page.-lpNgYAw.js} +1 -1
  274. package/public/assets/{page.CssmrzTT.js → page.11hpNKmq.js} +1 -1
  275. package/public/assets/{page.C72FTTJr.js → page.B-LxsnqU.js} +1 -1
  276. package/public/assets/{page.sOFOrxqq.js → page.B-dzeIUC.js} +1 -1
  277. package/public/assets/{page.CsxWmPoI.js → page.B-nBR75u.js} +1 -1
  278. package/public/assets/{page.Dx_GRRdT.js → page.B3QqvLep.js} +1 -1
  279. package/public/assets/{page.DPyOkPVo.js → page.B8xdkCWA.js} +1 -1
  280. package/public/assets/{page.DUI8kqOY.js → page.BFb7yUd8.js} +1 -1
  281. package/public/assets/page.BbPPdoas.js +1 -0
  282. package/public/assets/page.BdS20a1O.js +1 -0
  283. package/public/assets/{page.DibhF7f-.js → page.BfmI9KYe.js} +1 -1
  284. package/public/assets/{page.DX4Nno6M.js → page.BgGJovOg.js} +1 -1
  285. package/public/assets/{page.BnmTC5jG.js → page.C0gBvR4O.js} +1 -1
  286. package/public/assets/{page.D5gj98Oe.js → page.C6bYizL7.js} +1 -1
  287. package/public/assets/{page.vla5Mzki.js → page.C8sLEMa6.js} +1 -1
  288. package/public/assets/{page.CXd_R9yU.js → page.CCNsZBXp.js} +1 -1
  289. package/public/assets/{page.CQlVGoXg.js → page.CF_YSLwP.js} +1 -1
  290. package/public/assets/{page.BbO1pspY.js → page.CPTtRDxw.js} +1 -1
  291. package/public/assets/{page.D1oKew1j.js → page.CdMmlUEm.js} +1 -1
  292. package/public/assets/{page.DxMDTbSx.js → page.Cged1bOZ.js} +1 -1
  293. package/public/assets/{page.Cj5h09Zo.js → page.Ch647dVk.js} +1 -1
  294. package/public/assets/{page.DVIHRbxB.js → page.CpvXDkQF.js} +1 -1
  295. package/public/assets/{page.i4KDuWcM.js → page.CrBl7f8N.js} +1 -1
  296. package/public/assets/{page.BSLJ4tx5.js → page.CrP7niA2.js} +1 -1
  297. package/public/assets/{page.CnTop8Zt.js → page.CvUFZzXr.js} +1 -1
  298. package/public/assets/{page.O2ddJYoT.js → page.CyUqJPmx.js} +1 -1
  299. package/public/assets/{page.DBhu4Arl.js → page.D8Rp8fFR.js} +1 -1
  300. package/public/assets/{page.CPZav1_c.js → page.Df9qtrdG.js} +1 -1
  301. package/public/assets/{page.Qkkv8roc.js → page.DiL4Gwqe.js} +1 -1
  302. package/public/assets/{page.BdmJ3yY3.js → page.Dl1apZE2.js} +1 -1
  303. package/public/assets/{page.1qjqvrwX.js → page.HV0w1d5C.js} +1 -1
  304. package/public/assets/{page.ChX1NqmC.js → page.N1x4YL8_.js} +1 -1
  305. package/public/assets/{page.Dh5f0XJC.js → page.P2Ny5veR.js} +1 -1
  306. package/public/assets/{page.CSs3-gdt.js → page.fOOCr18J.js} +1 -1
  307. package/public/assets/{page.Ir7KvKcF.js → page.sUIPWIiF.js} +1 -1
  308. package/public/assets/{page.CzlQTyAL.js → page.tE7PpWYW.js} +1 -1
  309. package/public/assets/{page.DzZLqI5Q.js → page.uziaGZsT.js} +1 -1
  310. package/public/index.html +2 -2
  311. package/public/assets/EndpointPageClient.LyNNvZ4z.js +0 -6
  312. package/public/assets/index.BuPfZebu.css +0 -1
  313. package/public/assets/index.CShjRV_B.js +0 -89
  314. 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, INTERNAL_REQUEST_HEADER, AG_DEFAULT_TOOLS, AG_TOOL_SUFFIX } from "../config/appConstants.js";
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 { deriveSessionId } from "../utils/sessionManager.js";
6
+ import { resolveSessionId } from "../utils/sessionManager.js";
9
7
  import { proxyAwareFetch } from "../utils/proxyFetch.js";
10
- import { cleanJSONSchemaForAntigravity } from "../translator/helpers/geminiHelper.js";
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 MAX_ANTIGRAVITY_OUTPUT_TOKENS = 16384;
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
- const action = stream ? "streamGenerateContent?alt=sse" : "generateContent";
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"] || antigravityUserAgent(),
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?.trim() || this.generateProjectId();
48
-
49
- // Get base request data - handle both envelope and raw body
50
- const isEnvelope = body && body.request && (body.model || body.project);
51
- const req = isEnvelope ? body.request : (body || {});
52
- const {
53
- tools: _originalTools,
54
- toolConfig: _originalToolConfig,
55
- systemInstruction,
56
- // Strip OpenAI fields that Google hates
57
- max_tokens,
58
- stream: _unusedStream,
59
- messages: _unusedMessages,
60
- model: _unusedModel,
61
- stop: _unusedStop,
62
- ...requestWithoutTools
63
- } = req;
64
-
65
- // Process contents - ensure we don't lose them
66
- const rawContents = req.contents || requestWithoutTools.contents || [];
67
- let finalContents = rawContents.map(c => {
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
- if (c.parts?.some(p => p.functionResponse)) role = "user";
70
-
71
- const parts = (c.parts || []).filter(p => {
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
- return { ...c, role, parts };
78
- }).filter(c => c.parts && c.parts.length > 0);
79
-
80
- // Merge systemInstruction into the first user message for maximum compatibility
81
- if (systemInstruction?.parts?.length > 0) {
82
- const sysText = systemInstruction.parts.map(p => p.text).filter(Boolean).join("\n");
83
- if (sysText) {
84
- if (finalContents.length > 0 && finalContents[0].role === "user") {
85
- // Prepend to first user message
86
- finalContents[0].parts = [{ text: sysText + "\n\n" }, ...finalContents[0].parts];
87
- } else {
88
- // Create new first user message
89
- finalContents = [{ role: "user", parts: [{ text: sysText }] }, ...finalContents];
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
- const generationConfig = { ...(req.generationConfig || {}) };
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
- contents: finalContents,
250
+ ...requestWithoutTools,
121
251
  generationConfig,
122
- sessionId: req.sessionId || body.sessionId || deriveSessionId(credentials?.email || credentials?.connectionId),
123
- ...(tools?.length > 0 && { 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
- project: body.project || projectId,
265
+ ...body,
266
+ project: projectId,
129
267
  model: model,
130
- userAgent: body.userAgent || "antigravity",
131
- requestType: body.requestType || "agent",
132
- requestId: body.requestId || `agent-${crypto.randomUUID()}`,
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
- parseError(response, bodyText) {
227
- let message = bodyText;
228
- try {
229
- const json = JSON.parse(bodyText);
230
- message = json.error?.message || json.message || json.error || bodyText;
231
- } catch {}
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
- async execute({ model, body, stream, credentials, signal, log, proxyOptions = null }) {
248
- // Warm up/resolve version cache
249
- try {
250
- await resolveAntigravityVersion().catch(() => {});
251
- } catch {}
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
- if (isRateLimited || isForbiddenQuota) {
287
- // Return immediately to allow account rotation in the outer loop (chat.js) without testing other fallback URLs
288
- return { response, url, headers, transformedBody };
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
- if (response.status === HTTP_STATUS.SERVICE_UNAVAILABLE) {
292
- // Internal retry only for 503 Service Unavailable
293
- if (retryAttemptsByUrl[urlIndex] < MAX_AUTO_RETRIES) {
294
- retryAttemptsByUrl[urlIndex]++;
295
- const delay = 2000 * Math.pow(2, retryAttemptsByUrl[urlIndex] - 1);
296
- log?.warn?.("RETRY", `503 auto retry ${retryAttemptsByUrl[urlIndex]}/${MAX_AUTO_RETRIES} after ${delay/1000}s`);
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
- if (!response.ok) {
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
- return { response, url, headers, transformedBody };
310
- } catch (error) {
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
- throw lastError || new Error(`All ${fallbackCount} URLs failed with status ${lastStatus}`);
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 || "https://api.openai.com/v1";
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 || "https://api.anthropic.com/v1";
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"] = "2023-06-01";
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
- const tryRetry = async (urlIndex, statusKey, reason) => {
109
- // Don't retry internally on the same URL for 429/403 (account exhaustion)
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 ${delayMs / 1000}s`);
117
- await new Promise(resolve => setTimeout(resolve, delayMs));
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}`)) { urlIndex--; continue; }
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}`);