n9router 0.3.97 → 0.3.99

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 (288) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +3 -3
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/(dashboard)/dashboard/basic-chat/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/(dashboard)/dashboard/cli-tools/page.js +2 -2
  7. package/.next/standalone/.next/server/app/(dashboard)/dashboard/cli-tools/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/(dashboard)/dashboard/console-log/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/(dashboard)/dashboard/media-providers/[kind]/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/(dashboard)/dashboard/mitm/page.js +1 -1
  15. package/.next/standalone/.next/server/app/(dashboard)/dashboard/mitm/page.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/(dashboard)/dashboard/mitm/page_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/(dashboard)/dashboard/proxy-pools/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/(dashboard)/dashboard/quota/page.js +2 -2
  24. package/.next/standalone/.next/server/app/(dashboard)/dashboard/quota/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
  26. package/.next/standalone/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
  27. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  29. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  30. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  31. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  32. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  33. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  34. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  35. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  37. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  38. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  39. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  40. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  41. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  42. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  43. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  44. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  45. package/.next/standalone/.next/server/app/api/antigravity-tools/import/route.js +1 -1
  46. package/.next/standalone/.next/server/app/api/antigravity-tools/import-refresh-tokens/route.js +1 -1
  47. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/alias/route.js +2 -2
  48. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/cli-tools/claude-settings/route.js +2 -2
  50. package/.next/standalone/.next/server/app/api/cli-tools/codex-settings/route.js +2 -2
  51. package/.next/standalone/.next/server/app/api/cli-tools/copilot-settings/route.js +2 -2
  52. package/.next/standalone/.next/server/app/api/cli-tools/droid-settings/route.js +1 -1
  53. package/.next/standalone/.next/server/app/api/cli-tools/openclaw-settings/route.js +2 -2
  54. package/.next/standalone/.next/server/app/api/cli-tools/opencode-settings/route.js +1 -1
  55. package/.next/standalone/.next/server/app/api/oauth/[provider]/[action]/route.js +1 -1
  56. package/.next/standalone/.next/server/app/api/oauth/cursor/import/route.js +1 -1
  57. package/.next/standalone/.next/server/app/api/providers/[id]/models/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/providers/[id]/test/route.js +1 -1
  59. package/.next/standalone/.next/server/app/api/providers/test-batch/route.js +1 -1
  60. package/.next/standalone/.next/server/app/api/shutdown/route.js +1 -1
  61. package/.next/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/translator/send/route.js +1 -1
  63. package/.next/standalone/.next/server/app/api/translator/translate/route.js +1 -1
  64. package/.next/standalone/.next/server/app/api/usage/[connectionId]/route.js +1 -1
  65. package/.next/standalone/.next/server/app/api/v1/api/chat/route.js +1 -1
  66. package/.next/standalone/.next/server/app/api/v1/chat/completions/route.js +1 -1
  67. package/.next/standalone/.next/server/app/api/v1/embeddings/route.js +1 -1
  68. package/.next/standalone/.next/server/app/api/v1/messages/route.js +1 -1
  69. package/.next/standalone/.next/server/app/api/v1/responses/route.js +1 -1
  70. package/.next/standalone/.next/server/app/api/v1beta/models/[...path]/route.js +1 -1
  71. package/.next/standalone/.next/server/app/api/version/route.js +1 -1
  72. package/.next/standalone/.next/server/app/callback/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/callback.html +1 -1
  74. package/.next/standalone/.next/server/app/callback.rsc +3 -3
  75. package/.next/standalone/.next/server/app/callback.segments/_full.segment.rsc +3 -3
  76. package/.next/standalone/.next/server/app/callback.segments/_head.segment.rsc +1 -1
  77. package/.next/standalone/.next/server/app/callback.segments/_index.segment.rsc +3 -3
  78. package/.next/standalone/.next/server/app/callback.segments/_tree.segment.rsc +1 -1
  79. package/.next/standalone/.next/server/app/callback.segments/callback/__PAGE__.segment.rsc +1 -1
  80. package/.next/standalone/.next/server/app/callback.segments/callback.segment.rsc +1 -1
  81. package/.next/standalone/.next/server/app/dashboard/basic-chat.html +1 -1
  82. package/.next/standalone/.next/server/app/dashboard/basic-chat.rsc +5 -5
  83. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat/__PAGE__.segment.rsc +2 -2
  84. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat.segment.rsc +1 -1
  85. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  86. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  87. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/_full.segment.rsc +5 -5
  88. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/_head.segment.rsc +1 -1
  89. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/_index.segment.rsc +3 -3
  90. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/_tree.segment.rsc +1 -1
  91. package/.next/standalone/.next/server/app/dashboard/cli-tools.html +1 -1
  92. package/.next/standalone/.next/server/app/dashboard/cli-tools.rsc +5 -5
  93. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools/__PAGE__.segment.rsc +2 -2
  94. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools.segment.rsc +1 -1
  95. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  96. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  97. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/_full.segment.rsc +5 -5
  98. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/_head.segment.rsc +1 -1
  99. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/_index.segment.rsc +3 -3
  100. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/_tree.segment.rsc +1 -1
  101. package/.next/standalone/.next/server/app/dashboard/combos.html +1 -1
  102. package/.next/standalone/.next/server/app/dashboard/combos.rsc +5 -5
  103. package/.next/standalone/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos/__PAGE__.segment.rsc +2 -2
  104. package/.next/standalone/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos.segment.rsc +1 -1
  105. package/.next/standalone/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  106. package/.next/standalone/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  107. package/.next/standalone/.next/server/app/dashboard/combos.segments/_full.segment.rsc +5 -5
  108. package/.next/standalone/.next/server/app/dashboard/combos.segments/_head.segment.rsc +1 -1
  109. package/.next/standalone/.next/server/app/dashboard/combos.segments/_index.segment.rsc +3 -3
  110. package/.next/standalone/.next/server/app/dashboard/combos.segments/_tree.segment.rsc +1 -1
  111. package/.next/standalone/.next/server/app/dashboard/endpoint.html +1 -1
  112. package/.next/standalone/.next/server/app/dashboard/endpoint.rsc +5 -5
  113. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint/__PAGE__.segment.rsc +2 -2
  114. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint.segment.rsc +1 -1
  115. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  116. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  117. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/_full.segment.rsc +5 -5
  118. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/_head.segment.rsc +1 -1
  119. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/_index.segment.rsc +3 -3
  120. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/_tree.segment.rsc +1 -1
  121. package/.next/standalone/.next/server/app/dashboard/mitm.html +1 -1
  122. package/.next/standalone/.next/server/app/dashboard/mitm.rsc +5 -5
  123. package/.next/standalone/.next/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard/mitm/__PAGE__.segment.rsc +2 -2
  124. package/.next/standalone/.next/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard/mitm.segment.rsc +1 -1
  125. package/.next/standalone/.next/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  126. package/.next/standalone/.next/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  127. package/.next/standalone/.next/server/app/dashboard/mitm.segments/_full.segment.rsc +5 -5
  128. package/.next/standalone/.next/server/app/dashboard/mitm.segments/_head.segment.rsc +1 -1
  129. package/.next/standalone/.next/server/app/dashboard/mitm.segments/_index.segment.rsc +3 -3
  130. package/.next/standalone/.next/server/app/dashboard/mitm.segments/_tree.segment.rsc +1 -1
  131. package/.next/standalone/.next/server/app/dashboard/profile.html +1 -1
  132. package/.next/standalone/.next/server/app/dashboard/profile.rsc +5 -5
  133. package/.next/standalone/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile/__PAGE__.segment.rsc +2 -2
  134. package/.next/standalone/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile.segment.rsc +1 -1
  135. package/.next/standalone/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  136. package/.next/standalone/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  137. package/.next/standalone/.next/server/app/dashboard/profile.segments/_full.segment.rsc +5 -5
  138. package/.next/standalone/.next/server/app/dashboard/profile.segments/_head.segment.rsc +1 -1
  139. package/.next/standalone/.next/server/app/dashboard/profile.segments/_index.segment.rsc +3 -3
  140. package/.next/standalone/.next/server/app/dashboard/profile.segments/_tree.segment.rsc +1 -1
  141. package/.next/standalone/.next/server/app/dashboard/providers/new.html +1 -1
  142. package/.next/standalone/.next/server/app/dashboard/providers/new.rsc +5 -5
  143. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new/__PAGE__.segment.rsc +2 -2
  144. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new.segment.rsc +1 -1
  145. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers.segment.rsc +1 -1
  146. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  147. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  148. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/_full.segment.rsc +5 -5
  149. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/_head.segment.rsc +1 -1
  150. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/_index.segment.rsc +3 -3
  151. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/_tree.segment.rsc +1 -1
  152. package/.next/standalone/.next/server/app/dashboard/providers.html +1 -1
  153. package/.next/standalone/.next/server/app/dashboard/providers.rsc +5 -5
  154. package/.next/standalone/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers/__PAGE__.segment.rsc +2 -2
  155. package/.next/standalone/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers.segment.rsc +1 -1
  156. package/.next/standalone/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  157. package/.next/standalone/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  158. package/.next/standalone/.next/server/app/dashboard/providers.segments/_full.segment.rsc +5 -5
  159. package/.next/standalone/.next/server/app/dashboard/providers.segments/_head.segment.rsc +1 -1
  160. package/.next/standalone/.next/server/app/dashboard/providers.segments/_index.segment.rsc +3 -3
  161. package/.next/standalone/.next/server/app/dashboard/providers.segments/_tree.segment.rsc +1 -1
  162. package/.next/standalone/.next/server/app/dashboard/proxy-pools.html +1 -1
  163. package/.next/standalone/.next/server/app/dashboard/proxy-pools.rsc +5 -5
  164. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools/__PAGE__.segment.rsc +2 -2
  165. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools.segment.rsc +1 -1
  166. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  167. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  168. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/_full.segment.rsc +5 -5
  169. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/_head.segment.rsc +1 -1
  170. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/_index.segment.rsc +3 -3
  171. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/_tree.segment.rsc +1 -1
  172. package/.next/standalone/.next/server/app/dashboard/quota.html +2 -2
  173. package/.next/standalone/.next/server/app/dashboard/quota.rsc +6 -6
  174. package/.next/standalone/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota/__PAGE__.segment.rsc +3 -3
  175. package/.next/standalone/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota.segment.rsc +1 -1
  176. package/.next/standalone/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  177. package/.next/standalone/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  178. package/.next/standalone/.next/server/app/dashboard/quota.segments/_full.segment.rsc +6 -6
  179. package/.next/standalone/.next/server/app/dashboard/quota.segments/_head.segment.rsc +1 -1
  180. package/.next/standalone/.next/server/app/dashboard/quota.segments/_index.segment.rsc +3 -3
  181. package/.next/standalone/.next/server/app/dashboard/quota.segments/_tree.segment.rsc +1 -1
  182. package/.next/standalone/.next/server/app/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
  183. package/.next/standalone/.next/server/app/dashboard/settings/pricing.html +1 -1
  184. package/.next/standalone/.next/server/app/dashboard/settings/pricing.rsc +3 -3
  185. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/_full.segment.rsc +3 -3
  186. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/_head.segment.rsc +1 -1
  187. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/_index.segment.rsc +3 -3
  188. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/_tree.segment.rsc +1 -1
  189. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing/__PAGE__.segment.rsc +1 -1
  190. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing.segment.rsc +1 -1
  191. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings.segment.rsc +1 -1
  192. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/dashboard.segment.rsc +1 -1
  193. package/.next/standalone/.next/server/app/dashboard/translator.html +1 -1
  194. package/.next/standalone/.next/server/app/dashboard/translator.rsc +5 -5
  195. package/.next/standalone/.next/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard/translator/__PAGE__.segment.rsc +2 -2
  196. package/.next/standalone/.next/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard/translator.segment.rsc +1 -1
  197. package/.next/standalone/.next/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  198. package/.next/standalone/.next/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  199. package/.next/standalone/.next/server/app/dashboard/translator.segments/_full.segment.rsc +5 -5
  200. package/.next/standalone/.next/server/app/dashboard/translator.segments/_head.segment.rsc +1 -1
  201. package/.next/standalone/.next/server/app/dashboard/translator.segments/_index.segment.rsc +3 -3
  202. package/.next/standalone/.next/server/app/dashboard/translator.segments/_tree.segment.rsc +1 -1
  203. package/.next/standalone/.next/server/app/dashboard/usage.html +1 -1
  204. package/.next/standalone/.next/server/app/dashboard/usage.rsc +5 -5
  205. package/.next/standalone/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage/__PAGE__.segment.rsc +2 -2
  206. package/.next/standalone/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage.segment.rsc +1 -1
  207. package/.next/standalone/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  208. package/.next/standalone/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  209. package/.next/standalone/.next/server/app/dashboard/usage.segments/_full.segment.rsc +5 -5
  210. package/.next/standalone/.next/server/app/dashboard/usage.segments/_head.segment.rsc +1 -1
  211. package/.next/standalone/.next/server/app/dashboard/usage.segments/_index.segment.rsc +3 -3
  212. package/.next/standalone/.next/server/app/dashboard/usage.segments/_tree.segment.rsc +1 -1
  213. package/.next/standalone/.next/server/app/dashboard.html +1 -1
  214. package/.next/standalone/.next/server/app/dashboard.rsc +5 -5
  215. package/.next/standalone/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard/__PAGE__.segment.rsc +2 -2
  216. package/.next/standalone/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
  217. package/.next/standalone/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  218. package/.next/standalone/.next/server/app/dashboard.segments/_full.segment.rsc +5 -5
  219. package/.next/standalone/.next/server/app/dashboard.segments/_head.segment.rsc +1 -1
  220. package/.next/standalone/.next/server/app/dashboard.segments/_index.segment.rsc +3 -3
  221. package/.next/standalone/.next/server/app/dashboard.segments/_tree.segment.rsc +1 -1
  222. package/.next/standalone/.next/server/app/index.html +1 -1
  223. package/.next/standalone/.next/server/app/index.rsc +3 -3
  224. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  225. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  226. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  227. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  228. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  229. package/.next/standalone/.next/server/app/landing/page_client-reference-manifest.js +1 -1
  230. package/.next/standalone/.next/server/app/landing.html +1 -1
  231. package/.next/standalone/.next/server/app/landing.rsc +3 -3
  232. package/.next/standalone/.next/server/app/landing.segments/_full.segment.rsc +3 -3
  233. package/.next/standalone/.next/server/app/landing.segments/_head.segment.rsc +1 -1
  234. package/.next/standalone/.next/server/app/landing.segments/_index.segment.rsc +3 -3
  235. package/.next/standalone/.next/server/app/landing.segments/_tree.segment.rsc +1 -1
  236. package/.next/standalone/.next/server/app/landing.segments/landing/__PAGE__.segment.rsc +1 -1
  237. package/.next/standalone/.next/server/app/landing.segments/landing.segment.rsc +1 -1
  238. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  239. package/.next/standalone/.next/server/app/login.html +1 -1
  240. package/.next/standalone/.next/server/app/login.rsc +4 -4
  241. package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +4 -4
  242. package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
  243. package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +3 -3
  244. package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  245. package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  246. package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
  247. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  248. package/.next/standalone/.next/server/app-paths-manifest.json +3 -3
  249. package/.next/standalone/.next/server/chunks/318.js +2 -2
  250. package/.next/standalone/.next/server/chunks/3245.js +1 -1
  251. package/.next/standalone/.next/server/chunks/3646.js +1 -1
  252. package/.next/standalone/.next/server/chunks/3832.js +1 -1
  253. package/.next/standalone/.next/server/chunks/5573.js +1 -1
  254. package/.next/standalone/.next/server/chunks/{1098.js → 7491.js} +8 -8
  255. package/.next/standalone/.next/server/chunks/8220.js +1 -1
  256. package/.next/standalone/.next/server/chunks/869.js +1 -1
  257. package/.next/standalone/.next/server/chunks/9609.js +1 -1
  258. package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  259. package/.next/standalone/.next/server/pages/404.html +1 -1
  260. package/.next/standalone/.next/server/pages/500.html +1 -1
  261. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  262. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  263. package/.next/{static/chunks/1321-cf269630cd35f374.js → standalone/.next/static/chunks/1321-a1aa9f7980d68e31.js} +1 -1
  264. package/.next/standalone/.next/static/chunks/{384-4395af7f56148fe2.js → 5507-ee4bdb0d72f2bf5a.js} +10 -10
  265. package/.next/standalone/.next/static/chunks/app/(dashboard)/dashboard/cli-tools/page-c0a16328f9656c4a.js +1 -0
  266. package/.next/standalone/.next/static/chunks/app/(dashboard)/dashboard/mitm/page-1c784308396f8f41.js +1 -0
  267. package/.next/standalone/.next/static/chunks/app/(dashboard)/dashboard/quota/page-e2666b9d85a3dd55.js +1 -0
  268. package/.next/standalone/mitm/server.js +44 -8
  269. package/.next/standalone/mitm/tokenPool.js +149 -19
  270. package/.next/standalone/mitm/usageTracker.js +76 -0
  271. package/.next/standalone/package.json +1 -1
  272. package/.next/standalone/src/mitm/server.js +44 -8
  273. package/.next/{standalone/.next/static/chunks/1321-cf269630cd35f374.js → static/chunks/1321-a1aa9f7980d68e31.js} +1 -1
  274. package/.next/static/chunks/{384-4395af7f56148fe2.js → 5507-ee4bdb0d72f2bf5a.js} +10 -10
  275. package/.next/static/chunks/app/(dashboard)/dashboard/cli-tools/page-c0a16328f9656c4a.js +1 -0
  276. package/.next/static/chunks/app/(dashboard)/dashboard/mitm/page-1c784308396f8f41.js +1 -0
  277. package/.next/static/chunks/app/(dashboard)/dashboard/quota/page-e2666b9d85a3dd55.js +1 -0
  278. package/package.json +1 -1
  279. package/.next/standalone/.next/static/chunks/app/(dashboard)/dashboard/cli-tools/page-7c9916c696b32907.js +0 -1
  280. package/.next/standalone/.next/static/chunks/app/(dashboard)/dashboard/mitm/page-f54107ea6e406ea9.js +0 -1
  281. package/.next/standalone/.next/static/chunks/app/(dashboard)/dashboard/quota/page-9353bb8383654485.js +0 -1
  282. package/.next/static/chunks/app/(dashboard)/dashboard/cli-tools/page-7c9916c696b32907.js +0 -1
  283. package/.next/static/chunks/app/(dashboard)/dashboard/mitm/page-f54107ea6e406ea9.js +0 -1
  284. package/.next/static/chunks/app/(dashboard)/dashboard/quota/page-9353bb8383654485.js +0 -1
  285. /package/.next/standalone/.next/static/{3_VzVlqirsfVCEDfYRtW1 → gpb-csizE4h9ZQ3ZXLajG}/_buildManifest.js +0 -0
  286. /package/.next/standalone/.next/static/{3_VzVlqirsfVCEDfYRtW1 → gpb-csizE4h9ZQ3ZXLajG}/_ssgManifest.js +0 -0
  287. /package/.next/static/{3_VzVlqirsfVCEDfYRtW1 → gpb-csizE4h9ZQ3ZXLajG}/_buildManifest.js +0 -0
  288. /package/.next/static/{3_VzVlqirsfVCEDfYRtW1 → gpb-csizE4h9ZQ3ZXLajG}/_ssgManifest.js +0 -0
@@ -0,0 +1 @@
1
+ (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2326],{70044:(e,t,a)=>{Promise.resolve().then(a.bind(a,75228))},75228:(e,t,a)=>{"use strict";a.d(t,{default:()=>u});var s=a(95155),i=a(12115),o=a(35497),l=a(3534),r=a(28777),c=a(3176);let n="https://9router.com",d={claude:"/api/cli-tools/claude-settings",codex:"/api/cli-tools/codex-settings",opencode:"/api/cli-tools/opencode-settings",droid:"/api/cli-tools/droid-settings",openclaw:"/api/cli-tools/openclaw-settings"};function u({machineId:e}){let t,a,p,[v,h]=(0,i.useState)([]),[f,g]=(0,i.useState)(!0),[x,j]=(0,i.useState)(null),[w,E]=(0,i.useState)({}),[b,y]=(0,i.useState)(!1),[S,m]=(0,i.useState)(!1),[k,P]=(0,i.useState)(""),[N,A]=(0,i.useState)([]),[O,_]=(0,i.useState)({});(0,i.useEffect)(()=>{I(),M(),$(),C()},[]);let C=async()=>{try{let e=await Promise.all(Object.entries(d).map(async([e,t])=>{try{let a=await fetch(t),s=await a.json();return[e,s]}catch{return[e,null]}}));_(Object.fromEntries(e))}catch(e){console.log("Error fetching tool statuses:",e)}},M=async()=>{try{let[e,t]=await Promise.all([fetch("/api/settings"),fetch("/api/tunnel/status")]);if(e.ok){let t=await e.json();y(t.cloudEnabled||!1)}if(t.ok){let e=await t.json();m(e.enabled||!1),P(e.publicUrl||"")}}catch(e){console.log("Error loading settings:",e)}},$=async()=>{try{let e=await fetch("/api/keys");if(e.ok){let t=await e.json();A(t.keys||[])}}catch(e){console.log("Error fetching API keys:",e)}},I=async()=>{try{let e=await fetch("/api/providers"),t=await e.json();e.ok&&h(t.connections||[])}catch(e){console.log("Error fetching connections:",e)}finally{g(!1)}},Q=()=>v.filter(e=>!1!==e.isActive),T=(0,i.useCallback)((e,t,a)=>{E(s=>s[e]?.[t]===a?s:{...s,[e]:{...s[e],[t]:a}})},[]);if(f)return(0,s.jsxs)("div",{className:"flex flex-col gap-4",children:[(0,s.jsx)(o.Qv,{}),(0,s.jsx)(o.Qv,{}),(0,s.jsx)(o.Qv,{})]});let U=(t=Q(),a=[],p=new Set,t.forEach(e=>{let t=r.Xg[e.provider]||e.provider;(0,r.KC)(e.provider).forEach(s=>{let i=`${t}/${s.id}`;p.has(i)||(p.add(i),a.push({value:i,label:`${t}/${s.id}`,provider:e.provider,alias:t,connectionName:e.name,modelId:s.id}))})}),a).length>0,Z=Object.entries(l.dM);return(0,s.jsx)("div",{className:"flex flex-col gap-6",children:(0,s.jsx)("div",{className:"flex flex-col gap-4",children:Z.map(([e,t])=>((e,t)=>{let a={tool:t,isExpanded:x===e,onToggle:()=>j(x===e?null:e),baseUrl:S&&k?k:b&&n?n:window.location.origin,apiKeys:N};switch(e){case"claude":return(0,s.jsx)(c.Tk,{...a,activeProviders:Q(),modelMappings:w[e]||{},onModelMappingChange:(t,a)=>T(e,t,a),hasActiveProviders:U,cloudEnabled:b,initialStatus:O.claude},e);case"codex":return(0,s.jsx)(c.Ah,{...a,activeProviders:Q(),cloudEnabled:b,initialStatus:O.codex},e);case"opencode":return(0,s.jsx)(c.qO,{...a,activeProviders:Q(),cloudEnabled:b,initialStatus:O.opencode},e);case"droid":return(0,s.jsx)(c.ZM,{...a,activeProviders:Q(),hasActiveProviders:U,cloudEnabled:b,initialStatus:O.droid},e);case"openclaw":return(0,s.jsx)(c.yZ,{...a,activeProviders:Q(),hasActiveProviders:U,cloudEnabled:b,initialStatus:O.openclaw},e);default:return(0,s.jsx)(c.a7,{toolId:e,...a,activeProviders:Q(),cloudEnabled:b,tunnelEnabled:S},e)}})(e,t))})})}}},e=>{e.O(0,[2574,3862,123,5772,1321,5497,5507,8441,3794,7358],()=>e(e.s=70044)),_N_E=e.O()}]);
@@ -0,0 +1 @@
1
+ (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[6607],{23879:(e,s,t)=>{"use strict";t.d(s,{default:()=>r});var a=t(95155),n=t(12115),i=t(3534),o=t(28777),l=t(52679),c=t(3176);function r(){let[e,s]=(0,n.useState)([]),[t,r]=(0,n.useState)([]),[d,u]=(0,n.useState)({}),[h,p]=(0,n.useState)(!1),[f,w]=(0,n.useState)(null),[v,S]=(0,n.useState)({running:!1,certExists:!1,dnsStatus:{},hasCachedPassword:!1}),[g,k]=(0,n.useState)(!1),x=async()=>{try{let e=await fetch("/api/providers");if(e.ok){let t=await e.json();s(t.connections||[])}}catch{}},y=async()=>{try{let e=await fetch("/api/keys");if(e.ok){let s=await e.json();r(s.keys||[])}}catch{}},j=async()=>{try{let e=await fetch("/api/models/alias");if(e.ok){let s=await e.json();u(s.aliases||{})}}catch{}},m=async()=>{try{let e=await fetch("/api/settings");if(e.ok){let s=await e.json();p(s.cloudEnabled||!1),k(!!s.tokenSwapEnabled)}}catch{}};(0,n.useEffect)(()=>{(async()=>{await Promise.all([x(),y(),j(),m()])})()},[]);let E=()=>e.filter(e=>!1!==e.isActive),b=Object.entries(i.wn);return(0,a.jsxs)("div",{className:"flex flex-col gap-6",children:[(0,a.jsx)(c.tA,{apiKeys:t,cloudEnabled:h,onStatusChange:S}),(0,a.jsx)("div",{className:"flex flex-col gap-2",children:b.map(([s,i])=>(0,a.jsxs)(n.Fragment,{children:[(0,a.jsx)(c.kn,{tool:i,isExpanded:f===s,onToggle:()=>w(f===s?null:s),serverRunning:v.running,dnsActive:v.dnsStatus?.[s]||!1,hasCachedPassword:v.hasCachedPassword||!1,apiKeys:t,activeProviders:E(),hasActiveProviders:E().some(e=>(0,o.KC)(e.provider).length>0||(0,l.mq)(e.provider)||(0,l.gb)(e.provider)),modelAliases:d,cloudEnabled:h,tokenSwapActive:i.supportsTokenSwap&&g,onDnsChange:e=>S(s=>({...s,dnsStatus:e.dnsStatus??s.dnsStatus}))}),i.supportsTokenSwap&&(0,a.jsx)(c.xm,{tool:i,connections:e,serverRunning:v.running,dnsActive:v.dnsStatus?.[s]||!1,onToggle:e=>k(e),onRefreshConnections:x})]},s))})]})}},60817:(e,s,t)=>{Promise.resolve().then(t.bind(t,23879))}},e=>{e.O(0,[2574,3862,123,5772,1321,5497,5507,8441,3794,7358],()=>e(e.s=60817)),_N_E=e.O()}]);
@@ -0,0 +1 @@
1
+ (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3826],{40869:(e,t,r)=>{"use strict";r.d(t,{default:()=>x});var s=r(95155),a=r(12115),l=r(57250),n=r(57294);function i({quotas:e=[],compact:t=!1}){if(!e||0===e.length)return null;let r=t?"py-1.5 px-2":"py-2 px-3",a=t?"text-xs":"text-sm",l=t?"text-xs":"text-sm",o=t?"text-[10px] leading-tight":"text-xs";return(0,s.jsx)("div",{className:"overflow-x-auto",children:(0,s.jsxs)("table",{className:"w-full table-fixed text-left",children:[(0,s.jsxs)("colgroup",{children:[(0,s.jsx)("col",{className:"w-[30%]"})," ",(0,s.jsx)("col",{className:"w-[45%]"})," ",(0,s.jsx)("col",{className:"w-[25%]"})," "]}),(0,s.jsx)("tbody",{children:e.map((e,i)=>{let c=void 0!==e.remainingPercentage?Math.round(e.remainingPercentage):(0,n.$4)(e.used,e.total),d=c>70?{text:"text-green-600 dark:text-green-400",bg:"bg-green-500",bgLight:"bg-green-500/10",emoji:"\uD83D\uDFE2"}:c>=30?{text:"text-yellow-600 dark:text-yellow-400",bg:"bg-yellow-500",bgLight:"bg-yellow-500/10",emoji:"\uD83D\uDFE1"}:{text:"text-red-600 dark:text-red-400",bg:"bg-red-500",bgLight:"bg-red-500/10",emoji:"\uD83D\uDD34"},u=(0,n.mO)(e.resetAt),m=(0,n.sQ)(e.resetAt);return(0,s.jsxs)("tr",{className:"border-b border-black/5 dark:border-white/5 hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors",children:[(0,s.jsx)("td",{className:r,children:(0,s.jsxs)("div",{className:"flex items-center gap-1.5 min-w-0",children:[(0,s.jsx)("span",{className:"text-[10px] shrink-0",children:d.emoji}),(0,s.jsx)("span",{className:`${a} font-medium text-text-primary truncate`,children:e.name})]})}),(0,s.jsx)("td",{className:r,children:(0,s.jsxs)("div",{className:t?"space-y-1":"space-y-1.5",children:[(0,s.jsx)("div",{className:`${t?"h-1":"h-1.5"} rounded-full overflow-hidden border ${d.bgLight} ${0===c?"border-black/10 dark:border-white/10":"border-transparent"}`,children:(0,s.jsx)("div",{className:`h-full transition-all duration-300 ${d.bg}`,style:{width:`${Math.min(c,100)}%`}})}),(0,s.jsxs)("div",{className:`flex items-center justify-between ${t?"text-[10px]":"text-xs"}`,children:[(0,s.jsxs)("span",{className:"text-text-muted",children:[e.used.toLocaleString()," / ",e.total>0?e.total.toLocaleString():"∞"]}),(0,s.jsxs)("span",{className:`font-medium ${d.text}`,children:[c,"%"]})]})]})}),(0,s.jsx)("td",{className:r,children:"-"!==u||m?(0,s.jsxs)("div",{className:"space-y-0.5",children:["-"!==u&&(0,s.jsxs)("div",{className:`${l} text-text-primary font-medium`,children:["in ",u]}),m&&(0,s.jsx)("div",{className:`${o} text-text-muted`,children:m})]}):(0,s.jsx)("div",{className:`${l} text-text-muted italic`,children:"N/A"})})]},i)})})]})})}var o=r(98542),c=r(84588),d=r(92542),u=r(35497),m=r(52679);function x(){let[e,t]=(0,a.useState)([]),[r,x]=(0,a.useState)({}),[h,p]=(0,a.useState)({}),[f,g]=(0,a.useState)({}),[b,v]=(0,a.useState)(!0),[y,j]=(0,a.useState)(null),[w,N]=(0,a.useState)(!1),[k,$]=(0,a.useState)(60),[E,C]=(0,a.useState)(!0),[q,A]=(0,a.useState)(null),[P,S]=(0,a.useState)(null),[D,L]=(0,a.useState)(!1),[O,T]=(0,a.useState)(null),[I,_]=(0,a.useState)([]),M=(0,a.useRef)(null),R=(0,a.useRef)(null),U=(0,a.useCallback)(async()=>{try{let e=await fetch("/api/providers/client");if(!e.ok)throw Error("Failed to fetch connections");let r=(await e.json()).connections||[];return t(r),r}catch(e){return console.error("Error fetching connections:",e),t([]),[]}},[]),z=(0,a.useCallback)(async(e,t)=>{p(t=>({...t,[e]:!0})),g(t=>({...t,[e]:null}));try{console.log(`[ProviderLimits] Fetching quota for ${t} (${e})`);let r=await fetch(`/api/usage/${e}`);if(!r.ok){let s=(await r.json().catch(()=>({}))).error||r.statusText;if(404===r.status)return void console.warn(`[ProviderLimits] Connection not found for ${t}, skipping`);if(401===r.status){console.warn(`[ProviderLimits] Auth error for ${t}:`,s),x(t=>({...t,[e]:{quotas:[],message:s}}));return}throw Error(`HTTP ${r.status}: ${s}`)}let s=await r.json();console.log(`[ProviderLimits] Got quota for ${t}:`,s);let a=(0,n.W_)(t,s);x(t=>({...t,[e]:{quotas:a,plan:s.plan||null,message:s.message||null,raw:s}}))}catch(r){console.error(`[ProviderLimits] Error fetching quota for ${t} (${e}):`,r),g(t=>({...t,[e]:r.message||"Failed to fetch quota"}))}finally{p(t=>({...t,[e]:!1}))}},[]),F=(0,a.useCallback)(async(e,t)=>{await z(e,t),j(new Date)},[z]),K=(0,a.useCallback)(async e=>{if(confirm("Delete this connection?")){A(e);try{(await fetch(`/api/providers/${e}`,{method:"DELETE"})).ok&&(t(t=>t.filter(t=>t.id!==e)),x(t=>{let r={...t};return delete r[e],r}),p(t=>{let r={...t};return delete r[e],r}),g(t=>{let r={...t};return delete r[e],r}))}catch(e){console.error("Error deleting connection:",e)}finally{A(null)}}},[]),J=(0,a.useCallback)(async(e,r)=>{S(e);try{(await fetch(`/api/providers/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({isActive:r})})).ok&&t(t=>t.map(t=>t.id===e?{...t,isActive:r}:t))}catch(e){console.error("Error updating connection status:",e)}finally{S(null)}},[]),Q=(0,a.useCallback)(async e=>{if(!O?.id)return;let t=O.id,r=O.provider;try{(await fetch(`/api/providers/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).ok&&(await U(),L(!1),T(null),m.wb.includes(r)&&await z(t,r))}catch(e){console.error("Error saving connection:",e)}},[O,U,z]);(0,a.useEffect)(()=>{let e=!1;return fetch("/api/proxy-pools?isActive=true",{cache:"no-store"}).then(e=>e.json()).then(t=>{!e&&t?.proxyPools&&_(t.proxyPools)}).catch(()=>{}),()=>{e=!0}},[]);let W=(0,a.useCallback)(async()=>{if(!w){N(!0),$(60);try{let e=(await U()).filter(e=>m.wb.includes(e.provider)&&"oauth"===e.authType);await Promise.all(e.map(e=>z(e.id,e.provider))),j(new Date)}catch(e){console.error("Error refreshing all providers:",e)}finally{N(!1)}}},[w,U,z]);(0,a.useEffect)(()=>{(async()=>{C(!0);let e=await U();C(!1);let t=e.filter(e=>m.wb.includes(e.provider)&&"oauth"===e.authType),r={};t.forEach(e=>{r[e.id]=!0}),p(r),await Promise.all(t.map(e=>z(e.id,e.provider))),j(new Date)})()},[]),(0,a.useEffect)(()=>{if(!b){M.current&&(clearInterval(M.current),M.current=null),R.current&&(clearInterval(R.current),R.current=null);return}return M.current=setInterval(()=>{W()},6e4),R.current=setInterval(()=>{$(e=>e<=1?60:e-1)},1e3),()=>{M.current&&clearInterval(M.current),R.current&&clearInterval(R.current)}},[b,W]),(0,a.useEffect)(()=>{let e=()=>{document.hidden?(M.current&&(clearInterval(M.current),M.current=null),R.current&&(clearInterval(R.current),R.current=null)):b&&(M.current=setInterval(W,6e4),R.current=setInterval(()=>{$(e=>e<=1?60:e-1)},1e3))};return document.addEventListener("visibilitychange",e),()=>{document.removeEventListener("visibilitychange",e)}},[b,W]);let G=(0,a.useCallback)(()=>{if(!y)return"Never";let e=Math.floor((new Date-y)/6e4),t=Math.floor(e/60),r=Math.floor(t/24);return r>0?`${r}d ago`:t>0?`${t}h ago`:e>0?`${e}m ago`:"Just now"},[y]),H=[...e.filter(e=>m.wb.includes(e.provider)&&"oauth"===e.authType)].sort((e,t)=>{let r=m.wb.indexOf(e.provider),s=m.wb.indexOf(t.provider);return r!==s?r-s:e.provider.localeCompare(t.provider)});return(H.length,Object.values(r).filter(e=>e?.quotas?.length>0).length,Object.values(r).reduce((e,t)=>t?.quotas?e+ +!!t.quotas.some(e=>30>(0,n.$4)(e.used,e.total)&&e.total>0):e,0),E||0!==H.length)?(0,s.jsxs)("div",{className:"space-y-6",children:[(0,s.jsxs)("div",{className:"flex items-center justify-between flex-wrap gap-4",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[(0,s.jsx)("h2",{className:"text-xl font-semibold text-text-primary",children:"Provider Limits"}),(0,s.jsxs)("span",{className:"text-sm text-text-muted",children:["Last updated: ",G()]})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsxs)("button",{onClick:()=>v(e=>!e),className:"flex items-center gap-2 px-3 py-2 rounded-lg border border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/5 transition-colors",title:b?"Disable auto-refresh":"Enable auto-refresh",children:[(0,s.jsx)("span",{className:`material-symbols-outlined text-[18px] ${b?"text-primary":"text-text-muted"}`,children:b?"toggle_on":"toggle_off"}),(0,s.jsx)("span",{className:"text-sm text-text-primary",children:"Auto-refresh"}),b&&(0,s.jsxs)("span",{className:"text-xs text-text-muted",children:["(",k,"s)"]})]}),(0,s.jsx)(d.default,{variant:"secondary",size:"md",icon:"refresh",onClick:W,disabled:w,loading:w,children:"Refresh All"})]})]}),(0,s.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-3",children:H.map(e=>{let t=r[e.id],a=h[e.id],n=f[e.id],d=!1===e.isActive,u=q===e.id||P===e.id;return(0,s.jsxs)(c.default,{padding:"none",className:`min-w-0 ${d?"opacity-60":""}`,children:[(0,s.jsx)("div",{className:"px-4 py-3 border-b border-black/10 dark:border-white/10",children:(0,s.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 min-w-0",children:[(0,s.jsx)("div",{className:"w-8 h-8 shrink-0 rounded-md flex items-center justify-center overflow-hidden",children:(0,s.jsx)(l.A,{src:`/providers/${e.provider}.png`,alt:e.provider,size:32,className:"object-contain",fallbackText:e.provider?.slice(0,2).toUpperCase()||"PR"})}),(0,s.jsxs)("div",{className:"min-w-0",children:[(0,s.jsx)("h3",{className:"text-sm font-semibold text-text-primary capitalize truncate",children:e.provider}),e.name&&(0,s.jsx)("p",{className:"text-xs text-text-muted truncate",children:e.name})]})]}),(0,s.jsxs)("div",{className:"flex items-center gap-1 shrink-0",children:[(0,s.jsx)("button",{type:"button",onClick:()=>F(e.id,e.provider),disabled:a||u,className:"p-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 transition-colors disabled:opacity-50",title:"Refresh quota",children:(0,s.jsx)("span",{className:`material-symbols-outlined text-[18px] text-text-muted ${a?"animate-spin":""}`,children:"refresh"})}),(0,s.jsx)("button",{type:"button",onClick:()=>{T(e),L(!0)},disabled:u,className:"p-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-primary transition-colors disabled:opacity-50",title:"Edit connection",children:(0,s.jsx)("span",{className:"material-symbols-outlined text-[18px]",children:"edit"})}),(0,s.jsx)("button",{type:"button",onClick:()=>K(e.id),disabled:u,className:"p-1.5 rounded-lg hover:bg-red-500/10 text-red-500 transition-colors disabled:opacity-50",title:"Delete connection",children:(0,s.jsx)("span",{className:`material-symbols-outlined text-[18px] ${q===e.id?"animate-pulse":""}`,children:"delete"})}),(0,s.jsx)("div",{className:"inline-flex items-center pl-0.5",title:e.isActive??!0?"Disable connection":"Enable connection",children:(0,s.jsx)(o.default,{size:"sm",checked:e.isActive??!0,disabled:u,onChange:t=>J(e.id,t)})})]})]})}),(0,s.jsx)("div",{className:"px-3 py-3",children:a?(0,s.jsx)("div",{className:"text-center py-5 text-text-muted",children:(0,s.jsx)("span",{className:"material-symbols-outlined text-[28px] animate-spin",children:"progress_activity"})}):n?(0,s.jsxs)("div",{className:"text-center py-5",children:[(0,s.jsx)("span",{className:"material-symbols-outlined text-[28px] text-red-500",children:"error"}),(0,s.jsx)("p",{className:"mt-1.5 text-xs text-text-muted",children:n})]}):t?.message?(0,s.jsx)("div",{className:"text-center py-5",children:(0,s.jsx)("p",{className:"text-xs text-text-muted",children:t.message})}):(0,s.jsx)(i,{quotas:t?.quotas,compact:!0})})]},e.id)})}),(0,s.jsx)(u.wI,{isOpen:D,connection:O,proxyPools:I,onSave:Q,onClose:()=>{L(!1),T(null)}})]}):(0,s.jsx)(c.default,{padding:"lg",children:(0,s.jsxs)("div",{className:"text-center py-12",children:[(0,s.jsx)("span",{className:"material-symbols-outlined text-[64px] text-text-muted opacity-20",children:"cloud_off"}),(0,s.jsx)("h3",{className:"mt-4 text-lg font-semibold text-text-primary",children:"No Providers Connected"}),(0,s.jsx)("p",{className:"mt-2 text-sm text-text-muted max-w-md mx-auto",children:"Connect to providers with OAuth to track your API quota limits and usage."})]})})}},57294:(e,t,r)=>{"use strict";r.d(t,{$4:()=>n,W_:()=>i,mO:()=>a,sQ:()=>l});var s=r(45564);function a(e){if(!e)return"-";try{let t="string"==typeof e?new Date(e):e,r=new Date,s=t-r;if(s<=0)return"-";let a=Math.ceil(s/6e4);if(a<60)return`${a}m`;let l=Math.floor(a/60),n=a%60;if(l<24)return`${l}h ${n}m`;let i=Math.floor(l/24);return`${i}d ${l%24}h ${n}m`}catch(e){return"-"}}function l(e){if(!e)return null;try{let t="string"==typeof e?new Date(e):e,r=new Date,s=new Date(r.getFullYear(),r.getMonth(),r.getDate()),a=new Date(s);a.setDate(a.getDate()+1);let l=new Date(a);l.setDate(l.getDate()+1);let n="";n=t>=s&&t<a?"Today":t>=a&&t<l?"Tomorrow":t.toLocaleDateString("en-US",{month:"short",day:"numeric"});let i=t.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0});return`${n}, ${i}`}catch{return null}}function n(e,t){return t&&0!==t?!e||e<0?100:e>=t?0:Math.round((t-e)/t*100):0}function i(e,t){if(!t||"object"!=typeof t)return[];let r=[];try{switch(e.toLowerCase()){case"github":default:t.quotas&&Object.entries(t.quotas).forEach(([e,t])=>{r.push({name:e,used:t.used||0,total:t.total||0,resetAt:t.resetAt||null})});break;case"antigravity":t.quotas&&Object.entries(t.quotas).forEach(([e,t])=>{r.push({name:t.displayName||e,modelKey:e,used:t.used||0,total:t.total||0,resetAt:t.resetAt||null,remainingPercentage:t.remainingPercentage})});break;case"codex":t.quotas&&Object.entries(t.quotas).forEach(([e,t])=>{r.push({name:e,used:t.used||0,total:t.total||0,resetAt:t.resetAt||null})});break;case"kiro":t.quotas&&Object.entries(t.quotas).forEach(([e,t])=>{r.push({name:e,used:t.used||0,total:t.total||0,resetAt:t.resetAt||null})});break;case"claude":t.message?r.push({name:"error",used:0,total:0,resetAt:null,message:t.message}):t.quotas&&Object.entries(t.quotas).forEach(([e,t])=>{r.push({name:e,used:t.used||0,total:t.total||0,resetAt:t.resetAt||null})})}}catch(t){return console.error(`Error parsing quota data for ${e}:`,t),[]}let a=(0,s.KC)(e);if(a.length>0){let e=new Map(a.map((e,t)=>[e.id,t]));r.sort((t,r)=>{let s=t.modelKey||t.name,a=r.modelKey||r.name;return(e.get(s)??999)-(e.get(a)??999)})}return r}},68818:(e,t,r)=>{Promise.resolve().then(r.bind(r,40869)),Promise.resolve().then(r.bind(r,25086))}},e=>{e.O(0,[2574,3862,123,1321,5497,8441,3794,7358],()=>e(e.s=68818)),_N_E=e.O()}]);
@@ -4,11 +4,14 @@ const path = require("path");
4
4
  const dns = require("dns");
5
5
  const { promisify } = require("util");
6
6
  const { log, err } = require("./logger");
7
- const { TARGET_HOSTS, URL_PATTERNS, getToolForHost } = require("./config");
7
+ const { TARGET_HOSTS, URL_PATTERNS, getToolForHost, isTargetHost } = require("./config");
8
8
  const { DATA_DIR, MITM_DIR } = require("./paths");
9
9
  const { isTokenSwapEnabled, getAllActiveConnections, triggerRefreshIfNeeded,
10
- forceRefreshConnection, setCooldown, setAuthCooldown, setModelCooldown, getTokenSwapStrategy,
11
- parseQuotaCooldown, markAccountUsed, getConnectionLabel, getTokenSwapAvailabilitySummary } = require("./tokenPool");
10
+ forceRefreshConnection, setCooldown, setAuthCooldown, setModelCooldown,
11
+ recordStrike, recordModelStrike, clearStrikes, clearModelStrikes,
12
+ getTokenSwapStrategy,
13
+ parseQuotaCooldown, shouldImmediateQuotaCooldown,
14
+ markAccountUsed, getConnectionLabel, getTokenSwapAvailabilitySummary } = require("./tokenPool");
12
15
  const { getCertForDomain } = require("./cert/generate");
13
16
  const { buildInputOnlyRequestDetail, createTokenSwapUsageObserver, generateDetailId } = require("./usageTracker");
14
17
 
@@ -184,6 +187,7 @@ async function passthrough(req, res, bodyBuffer, onResponse) {
184
187
  async function tokenSwapForward(req, res, bodyBuffer, connections, model, strategy, provider, requestStartTime) {
185
188
  const targetHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
186
189
  const targetIP = await resolveTargetIP(targetHost);
190
+ let lastRetryResponse = null;
187
191
 
188
192
  for (let i = 0; i < connections.length; i++) {
189
193
  const originalConn = connections[i];
@@ -234,12 +238,28 @@ async function tokenSwapForward(req, res, bodyBuffer, connections, model, strate
234
238
  if (result.retry && result.retryType === "quota") {
235
239
  const cooldownMs = parseQuotaCooldown(result.body);
236
240
  const cdLabel = cooldownMs ? ` cooldown=${Math.ceil(cooldownMs / 60000)}m` : "";
241
+ const immediateCooldown = shouldImmediateQuotaCooldown(result.statusCode, result.body);
242
+ lastRetryResponse = {
243
+ statusCode: result.statusCode,
244
+ headers: result.headers,
245
+ body: result.body,
246
+ };
237
247
  if (strategy === "sticky" && model) {
238
- setModelCooldown(conn.id, model, cooldownMs);
239
- log(`⚠️ [token-swap] "${label}" → ${result.statusCode} model=${model} quota exhausted${cdLabel}, trying next...`);
248
+ if (immediateCooldown) {
249
+ setModelCooldown(conn.id, model, cooldownMs);
250
+ log(`⚠️ [token-swap] "${label}" → ${result.statusCode} model=${model} COOLDOWN${cdLabel}, trying next...`);
251
+ } else {
252
+ const locked = recordModelStrike(conn.id, model, cooldownMs);
253
+ log(`⚠️ [token-swap] "${label}" → ${result.statusCode} model=${model}${locked ? " LOCKED" : " strike"}${cdLabel}, trying next...`);
254
+ }
240
255
  } else {
241
- setCooldown(conn.id, cooldownMs);
242
- log(`⚠️ [token-swap] "${label}" → ${result.statusCode} account quota exhausted${cdLabel}, trying next...`);
256
+ if (immediateCooldown) {
257
+ setCooldown(conn.id, cooldownMs);
258
+ log(`⚠️ [token-swap] "${label}" → ${result.statusCode} COOLDOWN${cdLabel}, trying next...`);
259
+ } else {
260
+ const locked = recordStrike(conn.id, cooldownMs);
261
+ log(`⚠️ [token-swap] "${label}" → ${result.statusCode}${locked ? " LOCKED" : " strike"}${cdLabel}, trying next...`);
262
+ }
243
263
  }
244
264
  break;
245
265
  }
@@ -264,6 +284,11 @@ async function tokenSwapForward(req, res, bodyBuffer, connections, model, strate
264
284
  }
265
285
 
266
286
  setAuthCooldown(conn.id);
287
+ lastRetryResponse = {
288
+ statusCode: result.statusCode,
289
+ headers: result.headers,
290
+ body: result.body,
291
+ };
267
292
  log(`⚠️ [token-swap] "${label}" → 401 invalid_token, trying next...`);
268
293
  break;
269
294
  }
@@ -273,6 +298,9 @@ async function tokenSwapForward(req, res, bodyBuffer, connections, model, strate
273
298
  const successModelTag = model ? ` model=${model}` : "";
274
299
  const successStrategyTag = strategy === "sticky" ? ` sticky(use #${newCount})` : ` rr`;
275
300
  log(`✅ [token-swap] "${label}" → ${statusCode}${successModelTag}${successStrategyTag}`);
301
+ // Clear strikes on success — previous 429s were likely false positives
302
+ clearStrikes(conn.id);
303
+ if (model) clearModelStrikes(conn.id, model);
276
304
  markAccountUsed(conn.id);
277
305
  res.writeHead(statusCode, result.response.headers);
278
306
 
@@ -328,7 +356,14 @@ async function tokenSwapForward(req, res, bodyBuffer, connections, model, strate
328
356
  }
329
357
  }
330
358
 
331
- // All accounts exhausted
359
+ if (lastRetryResponse) {
360
+ log(`⚠️ [token-swap] exhausted ${connections.length} account(s), returning last retryable ${lastRetryResponse.statusCode}`);
361
+ res.writeHead(lastRetryResponse.statusCode, lastRetryResponse.headers);
362
+ res.end(lastRetryResponse.body);
363
+ return true;
364
+ }
365
+
366
+ // All accounts exhausted with no retryable upstream response captured
332
367
  return false;
333
368
  }
334
369
 
@@ -457,6 +492,7 @@ server.on("error", (e) => {
457
492
  });
458
493
 
459
494
  const shutdown = () => server.close(() => process.exit(0));
495
+ process.setMaxListeners(0);
460
496
  process.on("SIGTERM", shutdown);
461
497
  process.on("SIGINT", shutdown);
462
498
  if (process.platform === "win32") process.on("SIGBREAK", shutdown);
@@ -13,20 +13,62 @@ const { log } = require("./logger");
13
13
  const DB_FILE = path.join(DATA_DIR, "db.json");
14
14
  const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000; // refresh 5min before expiry
15
15
  const ROUTER_PORT = process.env.PORT || 20128;
16
- const DEFAULT_COOLDOWN_MS = 5 * 60 * 1000;
16
+ const DEFAULT_COOLDOWN_MS = 2 * 60 * 1000;
17
17
  const DEFAULT_AUTH_COOLDOWN_MS = 10 * 60 * 1000;
18
+ const DEFAULT_STRIKE_THRESHOLD = 3; // consecutive 429s before hard cooldown
19
+ const CAPACITY_EXHAUSTED_COOLDOWN_MS = 60 * 1000;
18
20
 
19
21
  // ── In-memory state ──────────────────────────────────────────
20
22
  const cooldownMap = {}; // { [connectionId]: expiresTimestamp } quota/general cooldown
21
23
  const authCooldownMap = {}; // { [connectionId]: expiresTimestamp } invalid_token/auth cooldown
22
24
  const modelCooldownMap = {}; // { [connectionId]: { [model]: expiresTimestamp } }
25
+ const strikeMap = {}; // { [connectionId]: consecutiveHitCount }
26
+ const modelStrikeMap = {}; // { [connectionId]: { [model]: consecutiveHitCount } }
23
27
  const rrState = {}; // { [provider]: roundRobinIndex }
24
28
 
25
- // ── Cooldown management ──────────────────────────────────────
29
+ // ── Strike + cooldown management ─────────────────────────────
30
+ // Upstream often returns false-positive 429s. Instead of locking
31
+ // an account on the first hit, we count consecutive strikes.
32
+ // Hard cooldown only triggers after STRIKE_THRESHOLD consecutive 429s.
33
+
34
+ function getStrikeThreshold() {
35
+ try {
36
+ if (!fs.existsSync(DB_FILE)) return DEFAULT_STRIKE_THRESHOLD;
37
+ const db = JSON.parse(fs.readFileSync(DB_FILE, "utf-8"));
38
+ return db.settings?.cooldownStrikeThreshold || DEFAULT_STRIKE_THRESHOLD;
39
+ } catch { return DEFAULT_STRIKE_THRESHOLD; }
40
+ }
41
+
42
+ /**
43
+ * Record a 429 strike for an account. Returns true if the account
44
+ * just entered hard cooldown (threshold reached), false if it's
45
+ * still within tolerance.
46
+ */
47
+ function recordStrike(connId, durationMs) {
48
+ const count = (strikeMap[connId] || 0) + 1;
49
+ strikeMap[connId] = count;
50
+ const threshold = getStrikeThreshold();
51
+
52
+ if (count >= threshold) {
53
+ const ms = durationMs || DEFAULT_COOLDOWN_MS;
54
+ cooldownMap[connId] = Date.now() + ms;
55
+ delete strikeMap[connId];
56
+ log(`⏸ [token-pool] cooldown: ${connId.slice(0, 8)}… for ${Math.ceil(ms / 60000)}m (after ${count} strikes)`);
57
+ return true;
58
+ }
59
+
60
+ log(`⚡ [token-pool] strike ${count}/${threshold}: ${connId.slice(0, 8)}… (not locked yet)`);
61
+ return false;
62
+ }
63
+
64
+ function clearStrikes(connId) {
65
+ delete strikeMap[connId];
66
+ }
26
67
 
27
68
  function setCooldown(connId, durationMs) {
28
69
  const ms = durationMs || DEFAULT_COOLDOWN_MS;
29
70
  cooldownMap[connId] = Date.now() + ms;
71
+ delete strikeMap[connId];
30
72
  log(`⏸ [token-pool] cooldown: ${connId.slice(0, 8)}… for ${Math.ceil(ms / 60000)}m`);
31
73
  }
32
74
 
@@ -59,15 +101,45 @@ function isInCooldown(connId) {
59
101
  return !!getCooldownState(connId);
60
102
  }
61
103
 
62
- // ── Per-model cooldown management ────────────────────────────
104
+ // ── Per-model strike + cooldown management ───────────────────
63
105
  // Tracks which account+model combinations are quota-exhausted.
64
- // Used by "sticky" strategy: whole account stays available for other models.
106
+ // Same strike-before-cooldown logic as account-level.
107
+
108
+ /**
109
+ * Record a 429 strike for a specific account+model. Returns true
110
+ * if the model just entered hard cooldown.
111
+ */
112
+ function recordModelStrike(connId, model, durationMs) {
113
+ const key = model || "__unknown__";
114
+ if (!modelStrikeMap[connId]) modelStrikeMap[connId] = {};
115
+ const count = (modelStrikeMap[connId][key] || 0) + 1;
116
+ modelStrikeMap[connId][key] = count;
117
+ const threshold = getStrikeThreshold();
118
+
119
+ if (count >= threshold) {
120
+ const ms = durationMs || DEFAULT_COOLDOWN_MS;
121
+ if (!modelCooldownMap[connId]) modelCooldownMap[connId] = {};
122
+ modelCooldownMap[connId][key] = Date.now() + ms;
123
+ delete modelStrikeMap[connId][key];
124
+ log(`⏸ [token-pool] model-cooldown: ${connId.slice(0, 8)}… model="${key}" for ${Math.ceil(ms / 60000)}m (after ${count} strikes)`);
125
+ return true;
126
+ }
127
+
128
+ log(`⚡ [token-pool] model-strike ${count}/${threshold}: ${connId.slice(0, 8)}… model="${key}" (not locked yet)`);
129
+ return false;
130
+ }
131
+
132
+ function clearModelStrikes(connId, model) {
133
+ const key = model || "__unknown__";
134
+ if (modelStrikeMap[connId]?.[key]) delete modelStrikeMap[connId][key];
135
+ }
65
136
 
66
137
  function setModelCooldown(connId, model, durationMs) {
67
138
  const ms = durationMs || DEFAULT_COOLDOWN_MS;
68
139
  const key = model || "__unknown__";
69
140
  if (!modelCooldownMap[connId]) modelCooldownMap[connId] = {};
70
141
  modelCooldownMap[connId][key] = Date.now() + ms;
142
+ if (modelStrikeMap[connId]?.[key]) delete modelStrikeMap[connId][key];
71
143
  log(`⏸ [token-pool] model-cooldown: ${connId.slice(0, 8)}… model="${key}" for ${Math.ceil(ms / 60000)}m`);
72
144
  }
73
145
 
@@ -84,6 +156,17 @@ function isModelExhausted(connId, model) {
84
156
  return true;
85
157
  }
86
158
 
159
+ function isStoredModelQuotaExhausted(connection, model) {
160
+ if (!connection || !model) return false;
161
+ const status = connection.modelQuotaStatus;
162
+ if (!status || typeof status !== "object") return false;
163
+
164
+ const modelStatus = status[model];
165
+ if (!modelStatus || typeof modelStatus !== "object") return false;
166
+
167
+ return modelStatus.exhausted === true || modelStatus.remainingPercentage === 0;
168
+ }
169
+
87
170
  // ── Strategy reader ───────────────────────────────────────────
88
171
 
89
172
  function getTokenSwapStrategy() {
@@ -97,17 +180,44 @@ function getTokenSwapStrategy() {
97
180
  // ── Quota cooldown parser ────────────────────────────────────
98
181
  // Antigravity error format: "Your quota will reset after 2h7m23s"
99
182
  function parseQuotaCooldown(errorBody) {
183
+ const raw = String(errorBody || "");
184
+
100
185
  try {
101
- const json = JSON.parse(errorBody);
186
+ const json = JSON.parse(raw);
102
187
  const msg = json?.error?.message || json?.message || "";
188
+ const reason = String(
189
+ json?.error?.details?.[0]?.reason ||
190
+ json?.error?.reason ||
191
+ json?.reason ||
192
+ ""
193
+ ).toUpperCase();
103
194
  const match = msg.match(/reset after (\d+h)?(\d+m)?(\d+s)?/i);
104
- if (!match) return null;
105
- let ms = 0;
106
- if (match[1]) ms += parseInt(match[1]) * 3600000;
107
- if (match[2]) ms += parseInt(match[2]) * 60000;
108
- if (match[3]) ms += parseInt(match[3]) * 1000;
109
- return ms > 0 ? ms : null;
110
- } catch { return null; }
195
+ if (match) {
196
+ let ms = 0;
197
+ if (match[1]) ms += parseInt(match[1]) * 3600000;
198
+ if (match[2]) ms += parseInt(match[2]) * 60000;
199
+ if (match[3]) ms += parseInt(match[3]) * 1000;
200
+ return ms > 0 ? ms : null;
201
+ }
202
+
203
+ if (
204
+ reason === "MODEL_CAPACITY_EXHAUSTED" ||
205
+ /no capacity available for model|model capacity exhausted|capacity exhausted/i.test(msg)
206
+ ) {
207
+ return CAPACITY_EXHAUSTED_COOLDOWN_MS;
208
+ }
209
+ } catch {
210
+ if (/no capacity available for model|model capacity exhausted|capacity exhausted/i.test(raw)) {
211
+ return CAPACITY_EXHAUSTED_COOLDOWN_MS;
212
+ }
213
+ }
214
+
215
+ return null;
216
+ }
217
+
218
+ function shouldImmediateQuotaCooldown(statusCode, errorBody) {
219
+ if (statusCode !== 503) return false;
220
+ return parseQuotaCooldown(errorBody) != null;
111
221
  }
112
222
 
113
223
  // ── Read connections from db.json (sync) ─────────────────────
@@ -183,6 +293,11 @@ function getTokenSwapAvailabilitySummary(provider, model) {
183
293
  continue;
184
294
  }
185
295
 
296
+ if (model && isStoredModelQuotaExhausted(connection, model)) {
297
+ reasons.modelCooldown += 1;
298
+ continue;
299
+ }
300
+
186
301
  if (strategy === "sticky" && model && isModelExhausted(connection.id, model)) {
187
302
  reasons.modelCooldown += 1;
188
303
  continue;
@@ -298,16 +413,24 @@ function getAllActiveConnections(provider, model) {
298
413
  if (connections.length <= 1) return connections;
299
414
 
300
415
  const strategy = getTokenSwapStrategy();
416
+ const hardAvailable = model
417
+ ? connections.filter(c => !isStoredModelQuotaExhausted(c, model))
418
+ : connections;
419
+
420
+ if (model && hardAvailable.length === 0) {
421
+ return [];
422
+ }
301
423
 
302
424
  if (strategy === "sticky") {
303
425
  // Sticky strategy: filter out accounts exhausted for this specific model,
304
426
  // then sort most-recently-used first so the same account sticks across requests.
305
427
  const available = model
306
- ? connections.filter(c => !isModelExhausted(c.id, model))
307
- : connections;
428
+ ? hardAvailable.filter(c => !isModelExhausted(c.id, model))
429
+ : hardAvailable;
308
430
 
309
- // If all accounts exhausted for this model, fall back to full list
310
- const pool = available.length > 0 ? available : connections;
431
+ // Runtime model cooldowns are temporary and may be false positives,
432
+ // so sticky mode can still fall back to hard-eligible accounts.
433
+ const pool = available.length > 0 ? available : hardAvailable;
311
434
 
312
435
  // Most-recently-used first (sticky within session)
313
436
  return [...pool].sort((a, b) => {
@@ -318,11 +441,13 @@ function getAllActiveConnections(provider, model) {
318
441
  });
319
442
  }
320
443
 
444
+ const roundRobinConnections = hardAvailable;
445
+
321
446
  // Round-robin strategy: sticky round-robin matching the main routing engine
322
447
  // (src/sse/services/auth.js). Least-recently-used account starts each request.
323
448
  const stickyLimit = getStickyLimit(provider);
324
449
 
325
- const byRecency = [...connections].sort((a, b) => {
450
+ const byRecency = [...roundRobinConnections].sort((a, b) => {
326
451
  if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
327
452
  if (!a.lastUsedAt) return 1;
328
453
  if (!b.lastUsedAt) return -1;
@@ -333,7 +458,7 @@ function getAllActiveConnections(provider, model) {
333
458
 
334
459
  if (current?.lastUsedAt && currentCount < stickyLimit) {
335
460
  // Keep current account first, rest sorted oldest-first for fallback
336
- const rest = connections.filter(c => c.id !== current.id).sort((a, b) => {
461
+ const rest = roundRobinConnections.filter(c => c.id !== current.id).sort((a, b) => {
337
462
  if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
338
463
  if (!a.lastUsedAt) return -1;
339
464
  if (!b.lastUsedAt) return 1;
@@ -343,7 +468,7 @@ function getAllActiveConnections(provider, model) {
343
468
  }
344
469
 
345
470
  // Rotate: least-recently-used first
346
- return [...connections].sort((a, b) => {
471
+ return [...roundRobinConnections].sort((a, b) => {
347
472
  if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
348
473
  if (!a.lastUsedAt) return -1;
349
474
  if (!b.lastUsedAt) return 1;
@@ -470,9 +595,14 @@ module.exports = {
470
595
  setCooldown,
471
596
  setAuthCooldown,
472
597
  setModelCooldown,
598
+ recordStrike,
599
+ recordModelStrike,
600
+ clearStrikes,
601
+ clearModelStrikes,
473
602
  isModelExhausted,
474
603
  getTokenSwapStrategy,
475
604
  parseQuotaCooldown,
605
+ shouldImmediateQuotaCooldown,
476
606
  markAccountUsed,
477
607
  getConnectionLabel,
478
608
  maskEmail,
@@ -150,6 +150,63 @@ function extractContentLength(chunk) {
150
150
  return total;
151
151
  }
152
152
 
153
+ function extractContentText(chunk) {
154
+ if (!chunk || typeof chunk !== "object") return { content: "", thinking: "" };
155
+
156
+ let content = "";
157
+ let thinking = "";
158
+
159
+ // Anthropic streaming
160
+ if (typeof chunk.delta?.text === "string") content += chunk.delta.text;
161
+ if (typeof chunk.delta?.thinking === "string") thinking += chunk.delta.thinking;
162
+
163
+ // OpenAI streaming
164
+ if (typeof chunk.choices?.[0]?.delta?.content === "string") content += chunk.choices[0].delta.content;
165
+ if (typeof chunk.choices?.[0]?.delta?.reasoning_content === "string") thinking += chunk.choices[0].delta.reasoning_content;
166
+
167
+ // OpenAI non-streaming
168
+ if (chunk.choices?.[0]?.message) {
169
+ const msg = chunk.choices[0].message;
170
+ if (typeof msg.content === "string") content += msg.content;
171
+ if (typeof msg.reasoning_content === "string") thinking += msg.reasoning_content;
172
+ }
173
+
174
+ // Anthropic content blocks
175
+ if (Array.isArray(chunk.content)) {
176
+ for (const item of chunk.content) {
177
+ if (typeof item?.text === "string" && item?.type !== "thinking") content += item.text;
178
+ if (item?.type === "thinking" && typeof item?.thinking === "string") thinking += item.thinking;
179
+ }
180
+ }
181
+
182
+ // OpenAI Responses API
183
+ if (chunk.response?.output_text && typeof chunk.response.output_text === "string") content += chunk.response.output_text;
184
+ if (chunk.response?.output && Array.isArray(chunk.response.output)) {
185
+ for (const item of chunk.response.output) {
186
+ if (typeof item?.content === "string") content += item.content;
187
+ if (Array.isArray(item?.content)) {
188
+ for (const part of item.content) {
189
+ if (typeof part?.text === "string") content += part.text;
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ // Gemini
196
+ const geminiParts = chunk.candidates?.[0]?.content?.parts || chunk.response?.candidates?.[0]?.content?.parts;
197
+ if (Array.isArray(geminiParts)) {
198
+ for (const part of geminiParts) {
199
+ if (part?.thought === true && typeof part?.text === "string") {
200
+ thinking += part.text;
201
+ } else if (typeof part?.text === "string") {
202
+ content += part.text;
203
+ }
204
+ }
205
+ }
206
+
207
+ return { content, thinking };
208
+ }
209
+
153
210
  function estimateInputTokens(body) {
154
211
  if (!body || typeof body !== "object") return 0;
155
212
  try {
@@ -412,11 +469,23 @@ function createTokenSwapUsageObserver({ provider, model, connectionId, accountLa
412
469
  let contentLength = 0;
413
470
  let usage = null;
414
471
  let finished = false;
472
+ let responseContent = "";
473
+ let thinkingContent = "";
474
+ let contentCapped = false;
475
+ const MAX_CAPTURE_BYTES = 512 * 1024;
415
476
 
416
477
  const processParsedChunk = (parsed) => {
417
478
  const extracted = extractUsage(parsed);
418
479
  if (extracted) usage = extracted;
419
480
  contentLength += extractContentLength(parsed);
481
+ if (!contentCapped) {
482
+ const text = extractContentText(parsed);
483
+ responseContent += text.content;
484
+ thinkingContent += text.thinking;
485
+ if (responseContent.length + thinkingContent.length > MAX_CAPTURE_BYTES) {
486
+ contentCapped = true;
487
+ }
488
+ }
420
489
  };
421
490
 
422
491
  const processSseLine = (line) => {
@@ -504,10 +573,17 @@ function createTokenSwapUsageObserver({ provider, model, connectionId, accountLa
504
573
  try {
505
574
  await persistRequestDetail({
506
575
  ...detailRecord,
576
+ status: "completed",
507
577
  tokens: usage,
508
578
  latency: {
509
579
  ttft: 0,
510
580
  total: requestStartTime ? Date.now() - requestStartTime : 0
581
+ },
582
+ providerResponse: "[SSE stream — content captured in response field]",
583
+ response: {
584
+ content: responseContent || null,
585
+ thinking: thinkingContent || null,
586
+ type: contentCapped ? "token_swap_capped" : "token_swap"
511
587
  }
512
588
  });
513
589
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n9router",
3
- "version": "0.3.96",
3
+ "version": "0.3.99",
4
4
  "description": "Self-hosted AI routing gateway — local proxy for Claude, Gemini, OpenAI and 40+ providers",
5
5
  "keywords": [
6
6
  "ai",
@@ -4,11 +4,14 @@ const path = require("path");
4
4
  const dns = require("dns");
5
5
  const { promisify } = require("util");
6
6
  const { log, err } = require("./logger");
7
- const { TARGET_HOSTS, URL_PATTERNS, getToolForHost } = require("./config");
7
+ const { TARGET_HOSTS, URL_PATTERNS, getToolForHost, isTargetHost } = require("./config");
8
8
  const { DATA_DIR, MITM_DIR } = require("./paths");
9
9
  const { isTokenSwapEnabled, getAllActiveConnections, triggerRefreshIfNeeded,
10
- forceRefreshConnection, setCooldown, setAuthCooldown, setModelCooldown, getTokenSwapStrategy,
11
- parseQuotaCooldown, markAccountUsed, getConnectionLabel, getTokenSwapAvailabilitySummary } = require("./tokenPool");
10
+ forceRefreshConnection, setCooldown, setAuthCooldown, setModelCooldown,
11
+ recordStrike, recordModelStrike, clearStrikes, clearModelStrikes,
12
+ getTokenSwapStrategy,
13
+ parseQuotaCooldown, shouldImmediateQuotaCooldown,
14
+ markAccountUsed, getConnectionLabel, getTokenSwapAvailabilitySummary } = require("./tokenPool");
12
15
  const { getCertForDomain } = require("./cert/generate");
13
16
  const { buildInputOnlyRequestDetail, createTokenSwapUsageObserver, generateDetailId } = require("./usageTracker");
14
17
 
@@ -184,6 +187,7 @@ async function passthrough(req, res, bodyBuffer, onResponse) {
184
187
  async function tokenSwapForward(req, res, bodyBuffer, connections, model, strategy, provider, requestStartTime) {
185
188
  const targetHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
186
189
  const targetIP = await resolveTargetIP(targetHost);
190
+ let lastRetryResponse = null;
187
191
 
188
192
  for (let i = 0; i < connections.length; i++) {
189
193
  const originalConn = connections[i];
@@ -234,12 +238,28 @@ async function tokenSwapForward(req, res, bodyBuffer, connections, model, strate
234
238
  if (result.retry && result.retryType === "quota") {
235
239
  const cooldownMs = parseQuotaCooldown(result.body);
236
240
  const cdLabel = cooldownMs ? ` cooldown=${Math.ceil(cooldownMs / 60000)}m` : "";
241
+ const immediateCooldown = shouldImmediateQuotaCooldown(result.statusCode, result.body);
242
+ lastRetryResponse = {
243
+ statusCode: result.statusCode,
244
+ headers: result.headers,
245
+ body: result.body,
246
+ };
237
247
  if (strategy === "sticky" && model) {
238
- setModelCooldown(conn.id, model, cooldownMs);
239
- log(`⚠️ [token-swap] "${label}" → ${result.statusCode} model=${model} quota exhausted${cdLabel}, trying next...`);
248
+ if (immediateCooldown) {
249
+ setModelCooldown(conn.id, model, cooldownMs);
250
+ log(`⚠️ [token-swap] "${label}" → ${result.statusCode} model=${model} COOLDOWN${cdLabel}, trying next...`);
251
+ } else {
252
+ const locked = recordModelStrike(conn.id, model, cooldownMs);
253
+ log(`⚠️ [token-swap] "${label}" → ${result.statusCode} model=${model}${locked ? " LOCKED" : " strike"}${cdLabel}, trying next...`);
254
+ }
240
255
  } else {
241
- setCooldown(conn.id, cooldownMs);
242
- log(`⚠️ [token-swap] "${label}" → ${result.statusCode} account quota exhausted${cdLabel}, trying next...`);
256
+ if (immediateCooldown) {
257
+ setCooldown(conn.id, cooldownMs);
258
+ log(`⚠️ [token-swap] "${label}" → ${result.statusCode} COOLDOWN${cdLabel}, trying next...`);
259
+ } else {
260
+ const locked = recordStrike(conn.id, cooldownMs);
261
+ log(`⚠️ [token-swap] "${label}" → ${result.statusCode}${locked ? " LOCKED" : " strike"}${cdLabel}, trying next...`);
262
+ }
243
263
  }
244
264
  break;
245
265
  }
@@ -264,6 +284,11 @@ async function tokenSwapForward(req, res, bodyBuffer, connections, model, strate
264
284
  }
265
285
 
266
286
  setAuthCooldown(conn.id);
287
+ lastRetryResponse = {
288
+ statusCode: result.statusCode,
289
+ headers: result.headers,
290
+ body: result.body,
291
+ };
267
292
  log(`⚠️ [token-swap] "${label}" → 401 invalid_token, trying next...`);
268
293
  break;
269
294
  }
@@ -273,6 +298,9 @@ async function tokenSwapForward(req, res, bodyBuffer, connections, model, strate
273
298
  const successModelTag = model ? ` model=${model}` : "";
274
299
  const successStrategyTag = strategy === "sticky" ? ` sticky(use #${newCount})` : ` rr`;
275
300
  log(`✅ [token-swap] "${label}" → ${statusCode}${successModelTag}${successStrategyTag}`);
301
+ // Clear strikes on success — previous 429s were likely false positives
302
+ clearStrikes(conn.id);
303
+ if (model) clearModelStrikes(conn.id, model);
276
304
  markAccountUsed(conn.id);
277
305
  res.writeHead(statusCode, result.response.headers);
278
306
 
@@ -328,7 +356,14 @@ async function tokenSwapForward(req, res, bodyBuffer, connections, model, strate
328
356
  }
329
357
  }
330
358
 
331
- // All accounts exhausted
359
+ if (lastRetryResponse) {
360
+ log(`⚠️ [token-swap] exhausted ${connections.length} account(s), returning last retryable ${lastRetryResponse.statusCode}`);
361
+ res.writeHead(lastRetryResponse.statusCode, lastRetryResponse.headers);
362
+ res.end(lastRetryResponse.body);
363
+ return true;
364
+ }
365
+
366
+ // All accounts exhausted with no retryable upstream response captured
332
367
  return false;
333
368
  }
334
369
 
@@ -457,6 +492,7 @@ server.on("error", (e) => {
457
492
  });
458
493
 
459
494
  const shutdown = () => server.close(() => process.exit(0));
495
+ process.setMaxListeners(0);
460
496
  process.on("SIGTERM", shutdown);
461
497
  process.on("SIGINT", shutdown);
462
498
  if (process.platform === "win32") process.on("SIGBREAK", shutdown);