@jsonstudio/rcc 0.89.1189 → 0.89.1348

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 (337) hide show
  1. package/README.md +17 -0
  2. package/configsamples/config.json +426 -0
  3. package/configsamples/config.reference.json +58 -0
  4. package/configsamples/provider/crs/config.v1.json +46 -0
  5. package/configsamples/provider/glm/config.v1.json +81 -0
  6. package/configsamples/provider/glm-anthropic/config.v1.json +45 -0
  7. package/configsamples/provider/iflow/config.v1.json +74 -0
  8. package/configsamples/provider/kimi/config.v1.json +41 -0
  9. package/configsamples/provider/lmstudio/config.v1.json +101 -0
  10. package/configsamples/provider/mimo/config.v1.json +35 -0
  11. package/configsamples/provider/modelscope/config.v1.json +96 -0
  12. package/configsamples/provider/qwen/config.v1.json +38 -0
  13. package/configsamples/provider/tab/config.v1.json +50 -0
  14. package/configsamples/provider/tabglm/config.v1.json +49 -0
  15. package/dist/build-info.js +2 -2
  16. package/dist/cli/commands/code.js +12 -6
  17. package/dist/cli/commands/code.js.map +1 -1
  18. package/dist/cli/commands/config.d.ts +2 -1
  19. package/dist/cli/commands/config.js +74 -103
  20. package/dist/cli/commands/config.js.map +1 -1
  21. package/dist/cli/commands/examples.js +6 -6
  22. package/dist/cli/commands/examples.js.map +1 -1
  23. package/dist/cli/commands/init.d.ts +28 -0
  24. package/dist/cli/commands/init.js +91 -0
  25. package/dist/cli/commands/init.js.map +1 -0
  26. package/dist/cli/commands/port.js +10 -2
  27. package/dist/cli/commands/port.js.map +1 -1
  28. package/dist/cli/commands/restart.js +5 -2
  29. package/dist/cli/commands/restart.js.map +1 -1
  30. package/dist/cli/commands/start.js +25 -22
  31. package/dist/cli/commands/start.js.map +1 -1
  32. package/dist/cli/commands/status.js +1 -0
  33. package/dist/cli/commands/status.js.map +1 -1
  34. package/dist/cli/commands/stop.js +1 -0
  35. package/dist/cli/commands/stop.js.map +1 -1
  36. package/dist/cli/config/bundled-docs.d.ts +20 -0
  37. package/dist/cli/config/bundled-docs.js +91 -0
  38. package/dist/cli/config/bundled-docs.js.map +1 -0
  39. package/dist/cli/config/init-config.d.ts +36 -0
  40. package/dist/cli/config/init-config.js +180 -0
  41. package/dist/cli/config/init-config.js.map +1 -0
  42. package/dist/cli/config/init-provider-catalog.d.ts +8 -0
  43. package/dist/cli/config/init-provider-catalog.js +187 -0
  44. package/dist/cli/config/init-provider-catalog.js.map +1 -0
  45. package/dist/cli/register/init-command.d.ts +3 -0
  46. package/dist/cli/register/init-command.js +5 -0
  47. package/dist/cli/register/init-command.js.map +1 -0
  48. package/dist/cli.js +28 -3
  49. package/dist/cli.js.map +1 -1
  50. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +1 -1
  51. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  52. package/dist/config/risk-control-config.d.ts +94 -0
  53. package/dist/config/risk-control-config.js +196 -0
  54. package/dist/config/risk-control-config.js.map +1 -0
  55. package/dist/constants/index.d.ts +6 -0
  56. package/dist/constants/index.js +13 -0
  57. package/dist/constants/index.js.map +1 -1
  58. package/dist/docs/daemon-admin-ui.html +2113 -190
  59. package/dist/index.js +0 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/manager/modules/health/index.d.ts +1 -1
  62. package/dist/manager/modules/quota/antigravity-quota-manager.d.ts +70 -0
  63. package/dist/manager/modules/quota/antigravity-quota-manager.js +442 -0
  64. package/dist/manager/modules/quota/antigravity-quota-manager.js.map +1 -0
  65. package/dist/manager/modules/quota/index.d.ts +3 -127
  66. package/dist/manager/modules/quota/index.js +2 -1093
  67. package/dist/manager/modules/quota/index.js.map +1 -1
  68. package/dist/manager/modules/quota/provider-key-normalization.d.ts +3 -0
  69. package/dist/manager/modules/quota/provider-key-normalization.js +155 -0
  70. package/dist/manager/modules/quota/provider-key-normalization.js.map +1 -0
  71. package/dist/manager/modules/quota/provider-quota-daemon.cooldown.d.ts +9 -0
  72. package/dist/manager/modules/quota/provider-quota-daemon.cooldown.js +115 -0
  73. package/dist/manager/modules/quota/provider-quota-daemon.cooldown.js.map +1 -0
  74. package/dist/manager/modules/quota/provider-quota-daemon.d.ts +77 -0
  75. package/dist/manager/modules/quota/provider-quota-daemon.events.d.ts +12 -0
  76. package/dist/manager/modules/quota/provider-quota-daemon.events.js +237 -0
  77. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -0
  78. package/dist/manager/modules/quota/provider-quota-daemon.js +404 -0
  79. package/dist/manager/modules/quota/provider-quota-daemon.js.map +1 -0
  80. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.d.ts +11 -0
  81. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +189 -0
  82. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -0
  83. package/dist/manager/modules/quota/provider-quota-daemon.snapshot.d.ts +8 -0
  84. package/dist/manager/modules/quota/provider-quota-daemon.snapshot.js +96 -0
  85. package/dist/manager/modules/quota/provider-quota-daemon.snapshot.js.map +1 -0
  86. package/dist/manager/modules/quota/provider-quota-daemon.view.d.ts +19 -0
  87. package/dist/manager/modules/quota/provider-quota-daemon.view.js +37 -0
  88. package/dist/manager/modules/quota/provider-quota-daemon.view.js.map +1 -0
  89. package/dist/manager/modules/routing/index.d.ts +1 -0
  90. package/dist/manager/modules/routing/index.js +11 -25
  91. package/dist/manager/modules/routing/index.js.map +1 -1
  92. package/dist/manager/quota/provider-quota-center.d.ts +2 -0
  93. package/dist/manager/quota/provider-quota-center.js +80 -82
  94. package/dist/manager/quota/provider-quota-center.js.map +1 -1
  95. package/dist/modules/llmswitch/bridge.d.ts +16 -18
  96. package/dist/modules/llmswitch/bridge.js +314 -71
  97. package/dist/modules/llmswitch/bridge.js.map +1 -1
  98. package/dist/modules/llmswitch/core-loader.d.ts +4 -2
  99. package/dist/modules/llmswitch/core-loader.js +32 -20
  100. package/dist/modules/llmswitch/core-loader.js.map +1 -1
  101. package/dist/modules/pipeline/utils/colored-logger.js +3 -2
  102. package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
  103. package/dist/modules/pipeline/utils/debug-logger.js +1 -1
  104. package/dist/modules/pipeline/utils/debug-logger.js.map +1 -1
  105. package/dist/providers/auth/iflow-cookie-auth.js +0 -2
  106. package/dist/providers/auth/iflow-cookie-auth.js.map +1 -1
  107. package/dist/providers/auth/oauth-lifecycle.js +2 -23
  108. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  109. package/dist/providers/core/config/camoufox-launcher.js +35 -4
  110. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  111. package/dist/providers/core/runtime/antigravity-quota-client.js +6 -3
  112. package/dist/providers/core/runtime/antigravity-quota-client.js.map +1 -1
  113. package/dist/providers/core/runtime/base-provider.d.ts +2 -2
  114. package/dist/providers/core/runtime/base-provider.js +74 -69
  115. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  116. package/dist/providers/core/runtime/gemini-cli-http-provider.js +6 -4
  117. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  118. package/dist/providers/core/runtime/http-request-executor.js +2 -2
  119. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  120. package/dist/providers/core/runtime/http-transport-provider.d.ts +14 -0
  121. package/dist/providers/core/runtime/http-transport-provider.js +111 -5
  122. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  123. package/dist/providers/core/runtime/provider-error-classifier.js +10 -0
  124. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  125. package/dist/providers/core/runtime/provider-factory.js +7 -5
  126. package/dist/providers/core/runtime/provider-factory.js.map +1 -1
  127. package/dist/providers/core/runtime/provider-runtime-metadata.d.ts +6 -0
  128. package/dist/providers/core/runtime/provider-runtime-metadata.js.map +1 -1
  129. package/dist/providers/core/runtime/responses-provider.d.ts +1 -7
  130. package/dist/providers/core/runtime/responses-provider.js +12 -93
  131. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  132. package/dist/providers/core/strategies/oauth-auth-code-flow.js +12 -8
  133. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  134. package/dist/providers/core/utils/http-client.js +16 -3
  135. package/dist/providers/core/utils/http-client.js.map +1 -1
  136. package/dist/providers/core/utils/provider-error-logger.d.ts +1 -1
  137. package/dist/providers/core/utils/provider-error-reporter.d.ts +3 -1
  138. package/dist/providers/core/utils/provider-error-reporter.js +3 -0
  139. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  140. package/dist/providers/core/utils/snapshot-writer.js +1 -4
  141. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  142. package/dist/providers/mock/mock-provider-runtime.js +57 -27
  143. package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
  144. package/dist/scripts/camoufox/launch-auth.mjs +193 -58
  145. package/dist/server/handlers/handler-utils.js +3 -2
  146. package/dist/server/handlers/handler-utils.js.map +1 -1
  147. package/dist/server/runtime/http-server/daemon-admin/auth-handler.d.ts +2 -0
  148. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +103 -0
  149. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -0
  150. package/dist/server/runtime/http-server/daemon-admin/auth-session.d.ts +5 -0
  151. package/dist/server/runtime/http-server/daemon-admin/auth-session.js +77 -0
  152. package/dist/server/runtime/http-server/daemon-admin/auth-session.js.map +1 -0
  153. package/dist/server/runtime/http-server/daemon-admin/auth-store.d.ts +18 -0
  154. package/dist/server/runtime/http-server/daemon-admin/auth-store.js +89 -0
  155. package/dist/server/runtime/http-server/daemon-admin/auth-store.js.map +1 -0
  156. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +1 -2
  157. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  158. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +226 -24
  159. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  160. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +47 -8
  161. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
  162. package/dist/server/runtime/http-server/daemon-admin/restart-handler.js +1 -1
  163. package/dist/server/runtime/http-server/daemon-admin/restart-handler.js.map +1 -1
  164. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +1 -1
  165. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -1
  166. package/dist/server/runtime/http-server/daemon-admin/status-handler.js +68 -4
  167. package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -1
  168. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +3 -4
  169. package/dist/server/runtime/http-server/daemon-admin-routes.js +9 -14
  170. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  171. package/dist/server/runtime/http-server/executor-metadata.js +1 -1
  172. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  173. package/dist/server/runtime/http-server/executor-response.js +0 -16
  174. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  175. package/dist/server/runtime/http-server/hub-shadow-compare.d.ts +18 -0
  176. package/dist/server/runtime/http-server/hub-shadow-compare.js +256 -0
  177. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -0
  178. package/dist/server/runtime/http-server/index.d.ts +7 -2
  179. package/dist/server/runtime/http-server/index.js +287 -49
  180. package/dist/server/runtime/http-server/index.js.map +1 -1
  181. package/dist/server/runtime/http-server/middleware.js +19 -1
  182. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  183. package/dist/server/runtime/http-server/request-executor.js +10 -19
  184. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  185. package/dist/server/runtime/http-server/routes.js +8 -2
  186. package/dist/server/runtime/http-server/routes.js.map +1 -1
  187. package/dist/server/runtime/http-server/session-dir.d.ts +2 -0
  188. package/dist/server/runtime/http-server/session-dir.js +59 -0
  189. package/dist/server/runtime/http-server/session-dir.js.map +1 -0
  190. package/dist/server/runtime/http-server/types.d.ts +0 -4
  191. package/dist/server/utils/utf8-chunk-buffer.js +6 -3
  192. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -1
  193. package/dist/server/utils/warmup-storm-tracker.js +1 -1
  194. package/dist/server/utils/warmup-storm-tracker.js.map +1 -1
  195. package/dist/server-factory.d.ts +6 -28
  196. package/dist/server-factory.js +8 -93
  197. package/dist/server-factory.js.map +1 -1
  198. package/dist/token-daemon/index.js +2 -2
  199. package/dist/token-daemon/index.js.map +1 -1
  200. package/dist/token-daemon/provider-registry.js +0 -1
  201. package/dist/token-daemon/provider-registry.js.map +1 -1
  202. package/dist/token-daemon/server-utils.js +8 -9
  203. package/dist/token-daemon/server-utils.js.map +1 -1
  204. package/dist/token-daemon/token-utils.js +1 -1
  205. package/dist/token-daemon/token-utils.js.map +1 -1
  206. package/dist/tools/semantic-replay.js +2 -2
  207. package/dist/tools/semantic-replay.js.map +1 -1
  208. package/dist/tools/stats-request-events.d.ts +1 -1
  209. package/dist/tools/stats-usage.js +6 -3
  210. package/dist/tools/stats-usage.js.map +1 -1
  211. package/dist/utils/errorsamples.d.ts +5 -0
  212. package/dist/utils/errorsamples.js +27 -0
  213. package/dist/utils/errorsamples.js.map +1 -0
  214. package/dist/utils/llms-engine-shadow.d.ts +19 -0
  215. package/dist/utils/llms-engine-shadow.js +209 -0
  216. package/dist/utils/llms-engine-shadow.js.map +1 -0
  217. package/dist/utils/runtime-versions.d.ts +1 -0
  218. package/dist/utils/runtime-versions.js +39 -0
  219. package/dist/utils/runtime-versions.js.map +1 -0
  220. package/docs/ARCHITECTURE.md +402 -0
  221. package/docs/CODEX_AND_CLAUDE_CODE.md +69 -0
  222. package/docs/CONFIG_ARCHITECTURE.md +517 -0
  223. package/docs/ERROR_HANDLING_AUDIT.md +0 -0
  224. package/docs/GCLI2API_PARITY_GAPS.md +98 -0
  225. package/docs/INSTALLATION_AND_QUICKSTART.md +74 -0
  226. package/docs/INSTRUCTION_MARKUP.md +89 -0
  227. package/docs/MODULE_ENHANCEMENT_SYSTEM.md +666 -0
  228. package/docs/PORTS.md +36 -0
  229. package/docs/PROVIDERS_BUILTIN.md +111 -0
  230. package/docs/PROVIDER_TYPES.md +55 -0
  231. package/docs/SERVERTOOL_CLOCK_DESIGN.md +233 -0
  232. package/docs/USAGE_HANDLING_ANALYSIS.md +335 -0
  233. package/docs/USER_CONFIG_PARSER_CHANGES.md +175 -0
  234. package/docs/V3_INBOUND_OUTBOUND_DESIGN.md +86 -0
  235. package/docs/VIRTUAL_ROUTER_PRIORITY_AND_HEALTH.md +125 -0
  236. package/docs/anthropic-request-golden-samples.md +50 -0
  237. package/docs/ccr-alignment-enhancetool.md +105 -0
  238. package/docs/chat-glm-500-analysis.md +79 -0
  239. package/docs/chat-request-golden-samples.md +42 -0
  240. package/docs/chat-semantic-expansion-plan.md +82 -0
  241. package/docs/cli-command-inventory.md +76 -0
  242. package/docs/codex-samples-replay.md +50 -0
  243. package/docs/daemon-admin-api-design.md +350 -0
  244. package/docs/daemon-admin-module-structure.md +169 -0
  245. package/docs/daemon-admin-ui.html +3394 -0
  246. package/docs/debug-system-design.md +734 -0
  247. package/docs/debugging/gemini-sse-root-cause.md +52 -0
  248. package/docs/debugging/sse_encoding_failure_analysis.md +53 -0
  249. package/docs/dry-run/README.md +721 -0
  250. package/docs/error-handling-v2.md +92 -0
  251. package/docs/exec-command-guard-policy.example.v1.json +42 -0
  252. package/docs/fixes/gemini-protocol-mapping.md +57 -0
  253. package/docs/fixes/oauth-portal-timing-fix.md +202 -0
  254. package/docs/fixes/web-search-hop3-fix.md +265 -0
  255. package/docs/glm-api-reference.md +390 -0
  256. package/docs/glm-chat-completions.md +1779 -0
  257. package/docs/glm-history-inline-images.md +44 -0
  258. package/docs/golden-ci-library.md +66 -0
  259. package/docs/lmstudio-dry-run-summary.md +203 -0
  260. package/docs/lmstudio-tool-calling.md +214 -0
  261. package/docs/mapping-tables/anthropic-to-openai.json +290 -0
  262. package/docs/mapping-tables/iflow-to-openai.json +215 -0
  263. package/docs/mapping-tables/openai-passthrough.json +190 -0
  264. package/docs/mapping-tables/openai-to-iflow.json +227 -0
  265. package/docs/monitoring/Design.md +61 -0
  266. package/docs/multi-token-auth-guide.md +66 -0
  267. package/docs/oauth-authentication-guide.md +168 -0
  268. package/docs/oauth-iflow-implementation.md +153 -0
  269. package/docs/pipeline-routing-report.md +209 -0
  270. package/docs/plans/manager-daemon/PLAN.md +86 -0
  271. package/docs/plans/provider-config-v2-plan.md +176 -0
  272. package/docs/plans/provider-runtime-manager-plan.md +209 -0
  273. package/docs/plans/transparent-429-failover.md +89 -0
  274. package/docs/plans/unified-hub-framework-v1.md +245 -0
  275. package/docs/provider-config-v2-ui-design.md +181 -0
  276. package/docs/provider-quota-design.md +129 -0
  277. package/docs/providers/gemini-provider.md +62 -0
  278. package/docs/providers/lmstudio-v2-migration-report.md +102 -0
  279. package/docs/providers/provider-composite-design.md +142 -0
  280. package/docs/providers/provider-composite-testing.md +98 -0
  281. package/docs/providers/provider-type-only-migration.md +111 -0
  282. package/docs/rccx-wasm-migration.md +74 -0
  283. package/docs/refactoring/architecture-comparison-diagram.md +140 -0
  284. package/docs/refactoring/compatibility-v2-architecture-design.md +738 -0
  285. package/docs/refactoring/workflow-compatibility-refactoring-design.md +361 -0
  286. package/docs/reports/routing-classification-report.json +24 -0
  287. package/docs/reports/routing-classification-report.md +18 -0
  288. package/docs/reports/thinking-keywords-report.json +19 -0
  289. package/docs/responses/README.md +156 -0
  290. package/docs/responses-generic-provider.md +86 -0
  291. package/docs/responses-passthrough-provider-design.md +202 -0
  292. package/docs/routing-awrr-health-weighted-round-robin.md +179 -0
  293. package/docs/routing-instructions.md +393 -0
  294. package/docs/stop-message-auto.md +225 -0
  295. package/docs/streaming-flow.html +30 -0
  296. package/docs/streaming-flow.md +182 -0
  297. package/docs/token-daemon-preview.html +490 -0
  298. package/docs/token-refresh-daemon-plan.md +269 -0
  299. package/docs/transformation-tables/Gemini-FinishReason/345/256/214/346/225/264/350/275/254/346/215/242/350/241/250.json +233 -0
  300. package/docs/transformation-tables/README.md +225 -0
  301. package/docs/transformation-tables/claude-code-router-anthropic-to-gemini.json +283 -0
  302. package/docs/transformation-tables/claude-code-router-anthropic-to-openai.json +208 -0
  303. package/docs/transformation-tables/claude-code-router-openai-to-anthropic.json +261 -0
  304. package/docs/transformation-tables/claude-code-router-openai-to-gemini.json +208 -0
  305. package/docs/transformation-tables/claude-code-router-openai-to-lmstudio.json +182 -0
  306. package/docs/transformation-tables/claude-code-router-openai-to-ollama.json +250 -0
  307. package/docs/transformation-tables/claude-code-router-openai-to-textgenwebui.json +295 -0
  308. package/docs/transformation-tables/claude-code-router-provider-conversions.json +193 -0
  309. package/docs/transformation-tables//345/256/214/346/225/264/347/232/204/345/267/245/345/205/267/346/211/247/350/241/214/346/265/201/347/250/213/350/275/254/346/215/242/350/241/250.json +299 -0
  310. package/docs/transformation-tables//345/257/271/350/257/235/345/216/206/345/217/262/347/273/264/346/212/244/345/210/206/346/236/220.md +134 -0
  311. package/docs/transformation-tables//345/267/245/345/205/267/350/260/203/347/224/250/346/250/241/345/274/217/345/210/206/346/236/220.md +158 -0
  312. package/docs/transformation-tables//347/212/266/346/200/201/347/256/241/347/220/206/351/234/200/346/261/202/345/210/206/346/236/220.md +175 -0
  313. package/docs/transformation-tables//351/235/231/346/200/201/350/241/250vs/345/212/250/346/200/201/345/210/206/346/236/220.md +189 -0
  314. package/docs/transformation-tables//351/235/231/346/200/201/350/241/250/345/207/206/347/241/256/346/200/247/350/257/204/344/274/260.md +179 -0
  315. package/docs/transformation-tables//351/235/236/346/265/201/345/274/217/345/234/272/346/231/257/345/210/206/346/236/220.md +189 -0
  316. package/docs/v2-architecture/IMPLEMENTATION-ROADMAP.md +367 -0
  317. package/docs/v2-architecture/OPTIMIZED-DESIGN.md +827 -0
  318. package/docs/v2-architecture/PRERUN-CONNECTION-DESIGN.md +716 -0
  319. package/docs/v2-architecture/README.md +551 -0
  320. package/docs/verification/modelscope-verify.md +59 -0
  321. package/docs/web-search-service-design.md +322 -0
  322. package/package.json +12 -7
  323. package/scripts/camoufox/launch-auth.mjs +193 -58
  324. package/scripts/monitor-diff.mjs +126 -0
  325. package/scripts/pack-mode.mjs +19 -1
  326. package/scripts/pack-rcc.mjs +63 -0
  327. package/scripts/unified-hub-shadow-compare.mjs +33 -13
  328. package/scripts/verify-e2e-toolcall.mjs +115 -26
  329. package/dist/modules/llmswitch/pipeline-registry.d.ts +0 -57
  330. package/dist/modules/llmswitch/pipeline-registry.js +0 -229
  331. package/dist/modules/llmswitch/pipeline-registry.js.map +0 -1
  332. package/dist/server/RouteCodexServer.d.ts +0 -13
  333. package/dist/server/RouteCodexServer.js +0 -25
  334. package/dist/server/RouteCodexServer.js.map +0 -1
  335. package/dist/v2/conversion/hub/snapshot-recorder.d.ts +0 -12
  336. package/dist/v2/conversion/hub/snapshot-recorder.js +0 -22
  337. package/dist/v2/conversion/hub/snapshot-recorder.js.map +0 -1
@@ -1,1094 +1,3 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { homedir } from 'node:os';
4
- import { fetchAntigravityQuotaSnapshot, loadAntigravityAccessToken } from '../../../providers/core/runtime/antigravity-quota-client.js';
5
- import { scanProviderTokenFiles } from '../../../providers/auth/token-scanner/index.js';
6
- import { resolveAntigravityApiBase } from '../../../providers/auth/antigravity-userinfo-helper.js';
7
- import { getProviderErrorCenter } from '../../../modules/llmswitch/bridge.js';
8
- import { readTokenFile, evaluateTokenState } from '../../../token-daemon/token-utils.js';
9
- import { applyErrorEvent as applyQuotaErrorEvent, applySuccessEvent as applyQuotaSuccessEvent, applyUsageEvent as applyQuotaUsageEvent, createInitialQuotaState, tickQuotaStateTime } from '../../quota/provider-quota-center.js';
10
- import { appendProviderErrorEvent, loadProviderQuotaSnapshot, saveProviderQuotaSnapshot } from '../../quota/provider-quota-store.js';
11
- export class QuotaManagerModule {
12
- id = 'quota';
13
- snapshot = {};
14
- antigravityTokens = new Map();
15
- refreshTimer = null;
16
- quotaRoutingEnabled = true;
17
- providerErrorCenter = null;
18
- async init(context) {
19
- this.snapshot = this.loadSnapshotFromDisk();
20
- this.quotaRoutingEnabled = context.quotaRoutingEnabled !== false;
21
- }
22
- async start() {
23
- // 启动时立即做一次最佳努力刷新,然后根据 token 过期时间和 15 分钟基准动态调度后续刷新。
24
- try {
25
- await this.refreshAllAntigravityQuotas();
26
- }
27
- catch {
28
- // ignore startup refresh failures
29
- }
30
- void this.scheduleNextRefresh().catch(() => {
31
- // ignore scheduling failures
32
- });
33
- }
34
- async stop() {
35
- if (this.refreshTimer) {
36
- clearTimeout(this.refreshTimer);
37
- this.refreshTimer = null;
38
- }
39
- this.saveSnapshotToDisk();
40
- }
41
- /**
42
- * 用于 antigravity:注册需要追踪配额的 alias/token。
43
- * 多次调用同一 alias 会覆盖最新配置。
44
- */
45
- registerAntigravityToken(alias, tokenFile, apiBase) {
46
- const cleanAlias = alias.trim();
47
- const cleanToken = tokenFile.trim();
48
- const cleanBase = apiBase.trim();
49
- if (!cleanAlias || !cleanToken || !cleanBase) {
50
- return;
51
- }
52
- this.antigravityTokens.set(cleanAlias, {
53
- alias: cleanAlias,
54
- tokenFile: cleanToken,
55
- apiBase: cleanBase
56
- });
57
- }
58
- /**
59
- * 用于 antigravity:根据 alias+model 更新配额快照。
60
- */
61
- updateAntigravityQuota(alias, quota) {
62
- const aliasId = alias.trim();
63
- if (!aliasId) {
64
- return;
65
- }
66
- const now = Date.now();
67
- const next = { ...this.snapshot };
68
- for (const [modelId, info] of Object.entries(quota.models)) {
69
- const key = this.buildAntigravityKey(aliasId, modelId);
70
- const record = {
71
- remainingFraction: Number.isFinite(info.remainingFraction) ? info.remainingFraction : null,
72
- fetchedAt: quota.fetchedAt
73
- };
74
- const resetAt = this.computeResetAt(info.resetTimeRaw);
75
- if (resetAt && resetAt > now) {
76
- record.resetAt = resetAt;
77
- }
78
- next[key] = record;
79
- const providerKey = `antigravity.${aliasId}.${modelId}`;
80
- if (record.remainingFraction !== null && record.remainingFraction > 0) {
81
- void this.emitQuotaRecoveryEvent(providerKey, modelId);
82
- }
83
- else {
84
- const cooldownHint = record.resetAt ? Math.max(0, record.resetAt - now) : undefined;
85
- void this.emitQuotaDepletedEvent(providerKey, modelId, cooldownHint);
86
- }
87
- }
88
- this.snapshot = next;
89
- this.saveSnapshotToDisk();
90
- }
91
- /**
92
- * 判断给定 providerKey+model 是否有可用配额(仅针对 antigravity 语义)。
93
- */
94
- hasQuotaForAntigravity(providerKey, modelId) {
95
- const alias = this.extractAntigravityAlias(providerKey);
96
- if (!alias || !modelId) {
97
- return true;
98
- }
99
- const key = this.buildAntigravityKey(alias, modelId);
100
- const record = this.snapshot[key];
101
- if (!record) {
102
- // 没有任何配额记录时视为“无配额”,禁止进入路由池。
103
- return false;
104
- }
105
- const now = Date.now();
106
- // 如果已经超过 resetAt,但尚未刷新到新一轮配额,视为配额状态未知,同样禁止。
107
- if (record.resetAt && record.resetAt <= now) {
108
- return false;
109
- }
110
- if (record.remainingFraction === null) {
111
- return false;
112
- }
113
- return record.remainingFraction > 0;
114
- }
115
- getRawSnapshot() {
116
- return { ...this.snapshot };
117
- }
118
- buildAntigravityKey(alias, modelId) {
119
- return `antigravity://${alias}/${modelId}`;
120
- }
121
- extractAntigravityAlias(providerKey) {
122
- if (!providerKey || typeof providerKey !== 'string') {
123
- return null;
124
- }
125
- const trimmed = providerKey.trim();
126
- if (!trimmed.toLowerCase().startsWith('antigravity.')) {
127
- return null;
128
- }
129
- const segments = trimmed.split('.');
130
- if (segments.length < 2) {
131
- return null;
132
- }
133
- return segments[1];
134
- }
135
- computeResetAt(raw) {
136
- if (!raw || typeof raw !== 'string' || !raw.trim()) {
137
- return undefined;
138
- }
139
- const value = raw.trim();
140
- try {
141
- const normalized = value.endsWith('Z') ? value.replace(/Z$/, '+00:00') : value;
142
- const parsed = Date.parse(normalized);
143
- if (!Number.isFinite(parsed) || parsed <= 0) {
144
- return undefined;
145
- }
146
- return parsed;
147
- }
148
- catch {
149
- return undefined;
150
- }
151
- }
152
- async refreshAllAntigravityQuotas() {
153
- await this.syncAntigravityTokensFromDisk();
154
- if (this.antigravityTokens.size === 0) {
155
- return;
156
- }
157
- for (const { alias, tokenFile, apiBase } of this.antigravityTokens.values()) {
158
- try {
159
- const accessToken = await loadAntigravityAccessToken(tokenFile);
160
- if (!accessToken) {
161
- continue;
162
- }
163
- const snapshot = await fetchAntigravityQuotaSnapshot(apiBase, accessToken);
164
- if (!snapshot) {
165
- continue;
166
- }
167
- this.updateAntigravityQuota(alias, snapshot);
168
- }
169
- catch {
170
- // 单个 alias 失败不影响其他 alias 的刷新
171
- }
172
- }
173
- }
174
- /**
175
- * 根据当前 token 池的过期时间和固定 15 分钟基准,动态安排下一次 quota 刷新:
176
- * - 如有 token 会在 15 分钟内到期,则在该 token 到期时间附近刷新;
177
- * - 否则按固定 15 分钟间隔刷新。
178
- */
179
- async scheduleNextRefresh() {
180
- if (this.refreshTimer) {
181
- clearTimeout(this.refreshTimer);
182
- this.refreshTimer = null;
183
- }
184
- const baseIntervalMs = 15 * 60 * 1000;
185
- let delayMs = baseIntervalMs;
186
- try {
187
- const nextExpiryDelay = await this.computeNextTokenExpiryDelayMs();
188
- if (nextExpiryDelay !== null && nextExpiryDelay > 0 && nextExpiryDelay < baseIntervalMs) {
189
- delayMs = nextExpiryDelay;
190
- }
191
- }
192
- catch {
193
- // 如果计算失败,退回到固定 15 分钟间隔
194
- delayMs = baseIntervalMs;
195
- }
196
- this.refreshTimer = setTimeout(() => {
197
- void this.refreshAllAntigravityQuotas()
198
- .catch(() => {
199
- // ignore refresh failure
200
- })
201
- .finally(() => {
202
- void this.scheduleNextRefresh().catch(() => {
203
- // ignore reschedule failure
204
- });
205
- });
206
- }, delayMs);
207
- }
208
- /**
209
- * 扫描 antigravity token 文件,计算距离最近一次 token 过期还剩多少毫秒。
210
- * 若所有 token 都无过期时间或已过期,则返回 null。
211
- */
212
- async computeNextTokenExpiryDelayMs() {
213
- let matches = [];
214
- try {
215
- const raw = await scanProviderTokenFiles('antigravity');
216
- matches = raw.map((m) => ({ filePath: m.filePath }));
217
- }
218
- catch {
219
- matches = [];
220
- }
221
- if (!matches.length) {
222
- return null;
223
- }
224
- const now = Date.now();
225
- let minDelay = null;
226
- for (const match of matches) {
227
- try {
228
- const token = await readTokenFile(match.filePath);
229
- const state = evaluateTokenState(token, now);
230
- const msLeft = state.msUntilExpiry;
231
- if (msLeft === null || msLeft <= 0) {
232
- continue;
233
- }
234
- if (minDelay === null || msLeft < minDelay) {
235
- minDelay = msLeft;
236
- }
237
- }
238
- catch {
239
- // ignore single token file errors
240
- }
241
- }
242
- return minDelay;
243
- }
244
- /**
245
- * 自动从本地 auth 目录扫描 antigravity OAuth token,并同步到内存注册表。
246
- * 这确保「每个 token」都能定期刷新 quota,而不依赖额外的显式注册流程。
247
- */
248
- async syncAntigravityTokensFromDisk() {
249
- let matches = [];
250
- try {
251
- matches = await scanProviderTokenFiles('antigravity');
252
- }
253
- catch {
254
- matches = [];
255
- }
256
- if (!matches.length) {
257
- this.antigravityTokens.clear();
258
- return;
259
- }
260
- const base = resolveAntigravityApiBase();
261
- const next = new Map();
262
- for (const match of matches) {
263
- const label = match.alias && match.alias !== 'default'
264
- ? `${match.sequence}-${match.alias}`
265
- : String(match.sequence);
266
- const alias = label.trim();
267
- if (!alias) {
268
- continue;
269
- }
270
- next.set(alias, {
271
- alias,
272
- tokenFile: match.filePath,
273
- apiBase: base
274
- });
275
- }
276
- // 若已有显式注册的 alias,保留其覆盖权
277
- for (const [alias, reg] of this.antigravityTokens.entries()) {
278
- if (!next.has(alias)) {
279
- next.set(alias, reg);
280
- }
281
- }
282
- this.antigravityTokens = next;
283
- }
284
- resolveStatePath() {
285
- const baseDir = path.join(homedir(), '.routecodex', 'state', 'quota');
286
- try {
287
- fs.mkdirSync(baseDir, { recursive: true });
288
- }
289
- catch {
290
- // best effort
291
- }
292
- return path.join(baseDir, 'antigravity.json');
293
- }
294
- loadSnapshotFromDisk() {
295
- const filePath = this.resolveStatePath();
296
- try {
297
- if (!fs.existsSync(filePath)) {
298
- return {};
299
- }
300
- const content = fs.readFileSync(filePath, 'utf8');
301
- const parsed = content.trim() ? JSON.parse(content) : {};
302
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
303
- return {};
304
- }
305
- const raw = parsed;
306
- const result = {};
307
- for (const [key, value] of Object.entries(raw)) {
308
- if (!value || typeof value !== 'object') {
309
- continue;
310
- }
311
- let remainingFraction = null;
312
- if (typeof value.remainingFraction === 'number') {
313
- remainingFraction = value.remainingFraction ?? null;
314
- }
315
- let resetAt;
316
- if (typeof value.resetAt === 'number') {
317
- resetAt = value.resetAt;
318
- }
319
- const fetchedAt = typeof value.fetchedAt === 'number'
320
- ? value.fetchedAt
321
- : Date.now();
322
- result[key] = { remainingFraction, resetAt, fetchedAt };
323
- }
324
- return result;
325
- }
326
- catch {
327
- return {};
328
- }
329
- }
330
- saveSnapshotToDisk() {
331
- const filePath = this.resolveStatePath();
332
- try {
333
- fs.writeFileSync(filePath, `${JSON.stringify(this.snapshot, null, 2)}\n`, 'utf8');
334
- }
335
- catch {
336
- // best effort
337
- }
338
- }
339
- async getProviderErrorCenterInstance() {
340
- if (this.providerErrorCenter) {
341
- return this.providerErrorCenter;
342
- }
343
- try {
344
- const center = await getProviderErrorCenter();
345
- if (center && typeof center.emit === 'function') {
346
- this.providerErrorCenter = center;
347
- }
348
- else {
349
- this.providerErrorCenter = null;
350
- }
351
- }
352
- catch {
353
- this.providerErrorCenter = null;
354
- }
355
- return this.providerErrorCenter;
356
- }
357
- async emitQuotaRecoveryEvent(providerKey, modelId) {
358
- if (!providerKey || !modelId) {
359
- return;
360
- }
361
- if (!this.quotaRoutingEnabled) {
362
- return;
363
- }
364
- const center = await this.getProviderErrorCenterInstance();
365
- if (!center) {
366
- return;
367
- }
368
- const now = Date.now();
369
- const event = {
370
- code: 'QUOTA_RECOVERY',
371
- message: 'Quota manager: provider quota refreshed',
372
- stage: 'quota',
373
- status: 200,
374
- recoverable: true,
375
- runtime: {
376
- requestId: `quota_${now}`,
377
- providerKey,
378
- providerId: 'antigravity'
379
- },
380
- timestamp: now,
381
- details: {
382
- virtualRouterQuotaRecovery: {
383
- providerKey,
384
- reason: `quota>0 for model ${modelId}`,
385
- source: 'quota-manager'
386
- }
387
- }
388
- };
389
- try {
390
- center.emit(event);
391
- }
392
- catch {
393
- // 忽略 error center 失败,避免影响配额刷新流程
394
- }
395
- }
396
- async emitQuotaDepletedEvent(providerKey, modelId, cooldownMs) {
397
- if (!providerKey || !modelId) {
398
- return;
399
- }
400
- if (!this.quotaRoutingEnabled) {
401
- return;
402
- }
403
- const center = await this.getProviderErrorCenterInstance();
404
- if (!center) {
405
- return;
406
- }
407
- const now = Date.now();
408
- const detail = {
409
- virtualRouterQuotaDepleted: {
410
- providerKey,
411
- reason: `quota<=0 for model ${modelId}`,
412
- ...(typeof cooldownMs === 'number' && cooldownMs > 0 ? { cooldownMs } : {})
413
- }
414
- };
415
- const event = {
416
- code: 'QUOTA_DEPLETED',
417
- message: 'Quota manager: provider quota exhausted',
418
- stage: 'quota',
419
- status: 429,
420
- recoverable: false,
421
- runtime: {
422
- requestId: `quota_${now}`,
423
- providerKey,
424
- providerId: 'antigravity'
425
- },
426
- timestamp: now,
427
- details: detail
428
- };
429
- try {
430
- center.emit(event);
431
- }
432
- catch {
433
- // ignore emit errors
434
- }
435
- }
436
- }
437
- export class ProviderQuotaDaemonModule {
438
- id = 'provider-quota';
439
- quotaStates = new Map();
440
- staticConfigs = new Map();
441
- unsubscribe = null;
442
- maintenanceTimer = null;
443
- persistTimer = null;
444
- quotaRoutingEnabled = true;
445
- async loadSnapshotIntoMemory() {
446
- // Always clear in-memory state first so operator actions like deleting provider-quota.json
447
- // take effect immediately after a reload.
448
- this.quotaStates = new Map();
449
- const snapshot = await loadProviderQuotaSnapshot();
450
- if (snapshot && snapshot.providers && typeof snapshot.providers === 'object') {
451
- for (const [providerKey, state] of Object.entries(snapshot.providers)) {
452
- if (state && typeof state === 'object') {
453
- this.quotaStates.set(providerKey, normalizeLoadedQuotaState(providerKey, state));
454
- }
455
- }
456
- }
457
- if (this.quotaStates.size) {
458
- const nowMs = Date.now();
459
- let changed = false;
460
- for (const [providerKey, state] of this.quotaStates.entries()) {
461
- const next = tickQuotaStateTime(state, nowMs);
462
- if (next !== state) {
463
- this.quotaStates.set(providerKey, next);
464
- changed = true;
465
- }
466
- }
467
- if (changed) {
468
- this.schedulePersist(nowMs);
469
- }
470
- }
471
- // Ensure we always have default entries for known providers (seeded via static configs),
472
- // so deleting the snapshot file doesn't permanently "hide" providers from admin views.
473
- if (this.staticConfigs.size) {
474
- const nowMs = Date.now();
475
- let seeded = false;
476
- for (const [providerKey, cfg] of this.staticConfigs.entries()) {
477
- if (this.quotaStates.has(providerKey)) {
478
- continue;
479
- }
480
- this.quotaStates.set(providerKey, createInitialQuotaState(providerKey, cfg, nowMs));
481
- seeded = true;
482
- }
483
- if (seeded) {
484
- this.schedulePersist(nowMs);
485
- }
486
- }
487
- }
488
- async init(context) {
489
- this.quotaRoutingEnabled = context.quotaRoutingEnabled !== false;
490
- try {
491
- await this.loadSnapshotIntoMemory();
492
- }
493
- catch {
494
- this.quotaStates = new Map();
495
- }
496
- }
497
- async reloadFromDisk() {
498
- await this.loadSnapshotIntoMemory();
499
- return { loadedAt: Date.now(), providerCount: this.quotaStates.size };
500
- }
501
- async reset(options = {}) {
502
- const nowMs = Date.now();
503
- this.quotaStates = new Map();
504
- // Rebuild default quota entries for known providers so routing can recover immediately.
505
- if (this.staticConfigs.size) {
506
- for (const [providerKey, cfg] of this.staticConfigs.entries()) {
507
- this.quotaStates.set(providerKey, createInitialQuotaState(providerKey, cfg, nowMs));
508
- }
509
- }
510
- const persisted = options.persist !== false;
511
- if (persisted) {
512
- try {
513
- await saveProviderQuotaSnapshot(this.toSnapshotObject(), new Date(nowMs));
514
- }
515
- catch {
516
- // ignore persistence failure
517
- }
518
- }
519
- return { resetAt: nowMs, persisted };
520
- }
521
- async resetProvider(providerKey) {
522
- const key = typeof providerKey === 'string' ? providerKey.trim() : '';
523
- if (!key) {
524
- return null;
525
- }
526
- const nowMs = Date.now();
527
- const next = createInitialQuotaState(key, this.staticConfigs.get(key), nowMs);
528
- this.quotaStates.set(key, next);
529
- try {
530
- await saveProviderQuotaSnapshot(this.toSnapshotObject(), new Date(nowMs));
531
- }
532
- catch {
533
- // ignore persistence failure
534
- }
535
- return { providerKey: key, state: next };
536
- }
537
- async recoverProvider(providerKey) {
538
- const key = typeof providerKey === 'string' ? providerKey.trim() : '';
539
- if (!key) {
540
- return null;
541
- }
542
- const nowMs = Date.now();
543
- const previous = this.quotaStates.get(key) ??
544
- createInitialQuotaState(key, this.staticConfigs.get(key), nowMs);
545
- const next = {
546
- ...previous,
547
- inPool: true,
548
- reason: 'ok',
549
- cooldownUntil: null,
550
- blacklistUntil: null,
551
- lastErrorSeries: null,
552
- consecutiveErrorCount: 0
553
- };
554
- this.quotaStates.set(key, next);
555
- try {
556
- await saveProviderQuotaSnapshot(this.toSnapshotObject(), new Date(nowMs));
557
- }
558
- catch {
559
- // ignore persistence failure
560
- }
561
- return { providerKey: key, state: next };
562
- }
563
- async disableProvider(options) {
564
- const key = typeof options?.providerKey === 'string' ? options.providerKey.trim() : '';
565
- if (!key) {
566
- return null;
567
- }
568
- const durationMs = typeof options.durationMs === 'number' && Number.isFinite(options.durationMs) && options.durationMs > 0
569
- ? Math.floor(options.durationMs)
570
- : 0;
571
- if (!durationMs) {
572
- return null;
573
- }
574
- const mode = options.mode === 'blacklist' ? 'blacklist' : 'cooldown';
575
- const nowMs = Date.now();
576
- const previous = this.quotaStates.get(key) ??
577
- createInitialQuotaState(key, this.staticConfigs.get(key), nowMs);
578
- const next = mode === 'blacklist'
579
- ? {
580
- ...previous,
581
- inPool: false,
582
- reason: 'blacklist',
583
- blacklistUntil: nowMs + durationMs,
584
- cooldownUntil: null
585
- }
586
- : {
587
- ...previous,
588
- inPool: false,
589
- reason: 'cooldown',
590
- cooldownUntil: nowMs + durationMs
591
- };
592
- this.quotaStates.set(key, next);
593
- try {
594
- await saveProviderQuotaSnapshot(this.toSnapshotObject(), new Date(nowMs));
595
- }
596
- catch {
597
- // ignore persistence failure
598
- }
599
- return { providerKey: key, state: next };
600
- }
601
- getAdminSnapshot() {
602
- return this.toSnapshotObject();
603
- }
604
- async start() {
605
- if (!this.quotaRoutingEnabled) {
606
- return;
607
- }
608
- let center = null;
609
- try {
610
- center = await getProviderErrorCenter();
611
- }
612
- catch {
613
- center = null;
614
- }
615
- if (center && typeof center.subscribe === 'function') {
616
- this.unsubscribe = center.subscribe((event) => {
617
- void this.handleProviderErrorEvent(event).catch(() => {
618
- // swallow handler errors to avoid unhandled rejection noise; quota updates are best-effort
619
- });
620
- });
621
- }
622
- const intervalMs = readPositiveNumberFromEnv('ROUTECODEX_QUOTA_DAEMON_INTERVAL_MS', 60_000);
623
- if (intervalMs > 0) {
624
- this.maintenanceTimer = setInterval(() => {
625
- void this.runMaintenanceTick().catch(() => {
626
- // ignore maintenance failures
627
- });
628
- }, intervalMs);
629
- }
630
- // Run a one-off maintenance tick immediately so expired cooldown/blacklist entries
631
- // are cleared even before the first request hits quotaView.
632
- void this.runMaintenanceTick().catch(() => {
633
- // ignore immediate tick failures
634
- });
635
- }
636
- async stop() {
637
- if (this.unsubscribe) {
638
- try {
639
- this.unsubscribe();
640
- }
641
- catch {
642
- // ignore unsubscribe failures
643
- }
644
- this.unsubscribe = null;
645
- }
646
- if (this.maintenanceTimer) {
647
- clearInterval(this.maintenanceTimer);
648
- this.maintenanceTimer = null;
649
- }
650
- if (this.persistTimer) {
651
- clearTimeout(this.persistTimer);
652
- this.persistTimer = null;
653
- }
654
- if (!this.quotaRoutingEnabled) {
655
- return;
656
- }
657
- try {
658
- await saveProviderQuotaSnapshot(this.toSnapshotObject(), new Date());
659
- }
660
- catch {
661
- // best-effort persistence
662
- }
663
- }
664
- recordProviderUsage(event) {
665
- if (!this.quotaRoutingEnabled) {
666
- return;
667
- }
668
- const providerKey = typeof event?.providerKey === 'string' ? event.providerKey.trim() : '';
669
- if (!providerKey) {
670
- return;
671
- }
672
- const nowMs = typeof event.timestampMs === 'number' && Number.isFinite(event.timestampMs) && event.timestampMs > 0
673
- ? event.timestampMs
674
- : Date.now();
675
- const requestedTokens = typeof event.requestedTokens === 'number' && Number.isFinite(event.requestedTokens) && event.requestedTokens > 0
676
- ? event.requestedTokens
677
- : 0;
678
- const previous = this.quotaStates.get(providerKey) ??
679
- createInitialQuotaState(providerKey, this.staticConfigs.get(providerKey), nowMs);
680
- const nextState = applyQuotaUsageEvent(previous, { providerKey, requestedTokens, timestampMs: nowMs }, nowMs);
681
- this.quotaStates.set(providerKey, nextState);
682
- this.schedulePersist(nowMs);
683
- }
684
- recordProviderSuccess(event) {
685
- if (!this.quotaRoutingEnabled) {
686
- return;
687
- }
688
- const providerKey = typeof event?.providerKey === 'string' ? event.providerKey.trim() : '';
689
- if (!providerKey) {
690
- return;
691
- }
692
- const nowMs = typeof event.timestampMs === 'number' && Number.isFinite(event.timestampMs) && event.timestampMs > 0
693
- ? event.timestampMs
694
- : Date.now();
695
- const usedTokens = typeof event.usedTokens === 'number' && Number.isFinite(event.usedTokens) && event.usedTokens > 0
696
- ? event.usedTokens
697
- : 0;
698
- const previous = this.quotaStates.get(providerKey) ??
699
- createInitialQuotaState(providerKey, this.staticConfigs.get(providerKey), nowMs);
700
- const nextState = applyQuotaSuccessEvent(previous, { providerKey, usedTokens, timestampMs: nowMs }, nowMs);
701
- this.quotaStates.set(providerKey, nextState);
702
- this.schedulePersist(nowMs);
703
- }
704
- registerProviderStaticConfig(providerKey, config = {}) {
705
- if (!this.quotaRoutingEnabled) {
706
- return;
707
- }
708
- const key = typeof providerKey === 'string' ? providerKey.trim() : '';
709
- if (!key) {
710
- return;
711
- }
712
- const authTypeRaw = typeof config.authType === 'string' ? config.authType.trim().toLowerCase() : '';
713
- const authType = authTypeRaw === 'apikey' ? 'apikey' : authTypeRaw === 'oauth' ? 'oauth' : 'unknown';
714
- const staticConfig = {
715
- ...(typeof config.priorityTier === 'number' && Number.isFinite(config.priorityTier)
716
- ? { priorityTier: config.priorityTier }
717
- : {}),
718
- authType
719
- };
720
- this.staticConfigs.set(key, staticConfig);
721
- const nowMs = Date.now();
722
- const existing = this.quotaStates.get(key);
723
- if (existing) {
724
- this.quotaStates.set(key, {
725
- ...existing,
726
- authType,
727
- ...(typeof staticConfig.priorityTier === 'number' ? { priorityTier: staticConfig.priorityTier } : {})
728
- });
729
- return;
730
- }
731
- // If snapshot is missing (or operator cleared it), we still want a predictable default:
732
- // API-key providers start "inPool: true" with unlimited quota until errors are observed.
733
- // We persist (debounced) so admin tools can inspect the pool quickly after startup.
734
- const initial = createInitialQuotaState(key, staticConfig, nowMs);
735
- // Antigravity OAuth providers require an explicit quota snapshot before being routed:
736
- // - If we haven't observed quota for a model yet, treat it as "no quota" and keep it out of pool.
737
- // - QuotaManagerModule will emit QUOTA_RECOVERY / QUOTA_DEPLETED to flip this state.
738
- // This prevents sticky/prefer targets from repeatedly hitting an untracked model and returning 429s.
739
- const isAntigravity = key.toLowerCase().startsWith('antigravity.');
740
- if (isAntigravity && authType === 'oauth') {
741
- this.quotaStates.set(key, {
742
- ...initial,
743
- inPool: false,
744
- reason: 'cooldown',
745
- cooldownUntil: null
746
- });
747
- }
748
- else {
749
- this.quotaStates.set(key, initial);
750
- }
751
- this.schedulePersist(nowMs);
752
- }
753
- async handleProviderErrorEvent(event) {
754
- if (!event) {
755
- return;
756
- }
757
- const code = typeof event.code === 'string' ? event.code : '';
758
- const providerKey = this.extractProviderKey(event);
759
- if (!providerKey) {
760
- return;
761
- }
762
- const nowMs = typeof event.timestamp === 'number' && Number.isFinite(event.timestamp) && event.timestamp > 0
763
- ? event.timestamp
764
- : Date.now();
765
- const previous = this.quotaStates.get(providerKey) ??
766
- createInitialQuotaState(providerKey, this.staticConfigs.get(providerKey), nowMs);
767
- // fatal 黑名单在锁定期内,不应被其它事件(包括 429/配额信号)改变。
768
- if (previous.reason === 'fatal' && previous.blacklistUntil && nowMs < previous.blacklistUntil) {
769
- return;
770
- }
771
- // QUOTA_* 属于“确定性配额信号”,不进入错误 series 统计。
772
- if (code === 'QUOTA_DEPLETED') {
773
- const detailCarrier = (event.details && typeof event.details === 'object') ? event.details : {};
774
- const raw = detailCarrier.virtualRouterQuotaDepleted;
775
- const cooldownMs = raw && typeof raw === 'object' && typeof raw.cooldownMs === 'number'
776
- ? raw.cooldownMs
777
- : undefined;
778
- const ttl = typeof cooldownMs === 'number' && Number.isFinite(cooldownMs) && cooldownMs > 0
779
- ? cooldownMs
780
- : undefined;
781
- const nextState = {
782
- ...previous,
783
- inPool: false,
784
- reason: 'quotaDepleted',
785
- cooldownUntil: ttl ? nowMs + ttl : previous.cooldownUntil
786
- };
787
- this.quotaStates.set(providerKey, nextState);
788
- this.schedulePersist(nowMs);
789
- return;
790
- }
791
- if (code === 'QUOTA_RECOVERY') {
792
- const withinBlacklist = previous.blacklistUntil !== null && nowMs < previous.blacklistUntil;
793
- const withinFatalBlacklist = previous.reason === 'fatal' && previous.blacklistUntil !== null && nowMs < previous.blacklistUntil;
794
- if (!withinBlacklist && !withinFatalBlacklist) {
795
- const nextState = {
796
- ...previous,
797
- inPool: true,
798
- reason: 'ok',
799
- cooldownUntil: null
800
- };
801
- this.quotaStates.set(providerKey, nextState);
802
- this.schedulePersist(nowMs);
803
- }
804
- return;
805
- }
806
- // Gemini-family quota exhausted errors often carry quota reset delay.
807
- // When present, treat as deterministic quota depletion signal rather than generic 429 backoff/blacklist.
808
- if (typeof event.status === 'number' && event.status === 429) {
809
- const seriesCooldownUntil = extractVirtualRouterSeriesCooldownUntil(event, nowMs);
810
- if (seriesCooldownUntil) {
811
- const nextState = {
812
- ...previous,
813
- inPool: false,
814
- reason: 'quotaDepleted',
815
- cooldownUntil: seriesCooldownUntil,
816
- blacklistUntil: null,
817
- lastErrorSeries: null,
818
- consecutiveErrorCount: 0
819
- };
820
- this.quotaStates.set(providerKey, nextState);
821
- this.schedulePersist(nowMs);
822
- return;
823
- }
824
- const runtime = event.runtime;
825
- const providerIdRaw = runtime && typeof runtime.providerId === 'string' ? runtime.providerId.trim().toLowerCase() : '';
826
- const isQuotaProvider = providerIdRaw === 'antigravity' || providerIdRaw === 'gemini-cli';
827
- if (isQuotaProvider) {
828
- const ttl = parseQuotaResetDelayMs(event);
829
- if (ttl && ttl > 0) {
830
- const nextState = {
831
- ...previous,
832
- inPool: false,
833
- reason: 'quotaDepleted',
834
- cooldownUntil: nowMs + ttl,
835
- blacklistUntil: null,
836
- lastErrorSeries: null,
837
- consecutiveErrorCount: 0
838
- };
839
- this.quotaStates.set(providerKey, nextState);
840
- this.schedulePersist(nowMs);
841
- return;
842
- }
843
- }
844
- }
845
- const errorForQuota = {
846
- providerKey,
847
- httpStatus: typeof event.status === 'number' ? event.status : undefined,
848
- code: typeof event.code === 'string' ? event.code : undefined,
849
- fatal: this.isFatalForQuota(event),
850
- timestampMs: nowMs
851
- };
852
- const nextState = applyQuotaErrorEvent(previous, errorForQuota, nowMs);
853
- this.quotaStates.set(providerKey, nextState);
854
- const tsIso = new Date(nowMs).toISOString();
855
- try {
856
- await appendProviderErrorEvent({
857
- ts: tsIso,
858
- providerKey,
859
- code: typeof errorForQuota.code === 'string' ? errorForQuota.code : undefined,
860
- httpStatus: typeof errorForQuota.httpStatus === 'number' ? errorForQuota.httpStatus : undefined,
861
- message: event.message,
862
- details: {
863
- stage: event.stage,
864
- routeName: event.runtime.routeName,
865
- entryEndpoint: event.runtime.entryEndpoint
866
- }
867
- });
868
- }
869
- catch {
870
- // logging failure is non-fatal
871
- }
872
- try {
873
- await saveProviderQuotaSnapshot(this.toSnapshotObject(), new Date(nowMs));
874
- }
875
- catch {
876
- // best-effort persistence only
877
- }
878
- }
879
- async runMaintenanceTick() {
880
- if (!this.quotaStates.size) {
881
- return;
882
- }
883
- const nowMs = Date.now();
884
- const updated = new Map();
885
- for (const [providerKey, state] of this.quotaStates.entries()) {
886
- const next = tickQuotaStateTime(state, nowMs);
887
- updated.set(providerKey, next);
888
- }
889
- this.quotaStates = updated;
890
- try {
891
- await saveProviderQuotaSnapshot(this.toSnapshotObject(), new Date(nowMs));
892
- }
893
- catch {
894
- // ignore persistence errors
895
- }
896
- }
897
- schedulePersist(_nowMs) {
898
- if (this.persistTimer) {
899
- return;
900
- }
901
- const debounceMs = readPositiveNumberFromEnv('ROUTECODEX_QUOTA_PERSIST_DEBOUNCE_MS', 5_000);
902
- this.persistTimer = setTimeout(() => {
903
- this.persistTimer = null;
904
- void saveProviderQuotaSnapshot(this.toSnapshotObject(), new Date()).catch(() => {
905
- // ignore persistence errors
906
- });
907
- }, debounceMs);
908
- }
909
- toSnapshotObject() {
910
- const result = {};
911
- for (const [key, state] of this.quotaStates.entries()) {
912
- result[key] = state;
913
- }
914
- return result;
915
- }
916
- extractProviderKey(event) {
917
- const runtime = event.runtime;
918
- const direct = runtime && typeof runtime.providerKey === 'string' && runtime.providerKey.trim()
919
- ? runtime.providerKey.trim()
920
- : null;
921
- if (direct) {
922
- return direct;
923
- }
924
- const target = runtime && runtime.target;
925
- if (target && typeof target === 'object') {
926
- const targetKey = target.providerKey;
927
- if (typeof targetKey === 'string' && targetKey.trim()) {
928
- return targetKey.trim();
929
- }
930
- }
931
- return null;
932
- }
933
- getQuotaView() {
934
- if (!this.quotaRoutingEnabled) {
935
- return () => null;
936
- }
937
- return (providerKey) => {
938
- const key = typeof providerKey === 'string' ? providerKey.trim() : '';
939
- if (!key) {
940
- return null;
941
- }
942
- const state = this.quotaStates.get(key);
943
- if (!state) {
944
- return null;
945
- }
946
- // 视图层做一次“即时修复”,确保即使 maintenance tick 未运行,
947
- // 冷却/黑名单到期也能立刻恢复可用状态,避免路由长期卡死。
948
- const nowMs = Date.now();
949
- const normalized = tickQuotaStateTime(state, nowMs);
950
- if (normalized !== state) {
951
- this.quotaStates.set(key, normalized);
952
- this.schedulePersist(nowMs);
953
- }
954
- const effective = normalized;
955
- return {
956
- providerKey: effective.providerKey,
957
- inPool: effective.inPool,
958
- reason: effective.reason,
959
- priorityTier: effective.priorityTier,
960
- cooldownUntil: effective.cooldownUntil ?? null,
961
- blacklistUntil: effective.blacklistUntil ?? null
962
- };
963
- };
964
- }
965
- isFatalForQuota(event) {
966
- const status = typeof event.status === 'number' ? event.status : undefined;
967
- const code = typeof event.code === 'string' ? event.code.toUpperCase() : '';
968
- const stage = typeof event.stage === 'string' ? event.stage.toLowerCase() : '';
969
- if (status === 401 || status === 402 || status === 403) {
970
- return true;
971
- }
972
- if (code.includes('AUTH') || code.includes('UNAUTHORIZED')) {
973
- return true;
974
- }
975
- if (code.includes('CONFIG')) {
976
- return true;
977
- }
978
- if (stage.includes('compat')) {
979
- return true;
980
- }
981
- if (event.recoverable === false && status !== undefined && status >= 500) {
982
- return true;
983
- }
984
- return false;
985
- }
986
- }
987
- function extractVirtualRouterSeriesCooldownUntil(event, nowMs) {
988
- if (!event || !event.details || typeof event.details !== 'object') {
989
- return null;
990
- }
991
- const raw = event.details.virtualRouterSeriesCooldown;
992
- if (!raw || typeof raw !== 'object') {
993
- return null;
994
- }
995
- const record = raw;
996
- const cooldownMsRaw = record.cooldownMs;
997
- const expiresAtRaw = record.expiresAt;
998
- const expiresAt = typeof expiresAtRaw === 'number' && Number.isFinite(expiresAtRaw) && expiresAtRaw > nowMs
999
- ? expiresAtRaw
1000
- : null;
1001
- if (expiresAt) {
1002
- return expiresAt;
1003
- }
1004
- const cooldownMs = typeof cooldownMsRaw === 'number'
1005
- ? cooldownMsRaw
1006
- : typeof cooldownMsRaw === 'string'
1007
- ? Number.parseFloat(cooldownMsRaw)
1008
- : Number.NaN;
1009
- if (!Number.isFinite(cooldownMs) || cooldownMs <= 0) {
1010
- return null;
1011
- }
1012
- return nowMs + cooldownMs;
1013
- }
1014
- function parseQuotaResetDelayMs(event) {
1015
- const message = typeof event.message === 'string' ? event.message : '';
1016
- const raw = message.toLowerCase();
1017
- // Common shape: "reset after 3h22m41s" (Gemini quota exhausted)
1018
- const afterMatch = raw.match(/reset after\s+([0-9a-z.\s]+)\.?/i);
1019
- if (afterMatch && afterMatch[1]) {
1020
- const parsed = parseDurationToMs(afterMatch[1]);
1021
- if (parsed && parsed > 0) {
1022
- return parsed;
1023
- }
1024
- }
1025
- // Sometimes the upstream JSON is embedded; try extracting quotaResetDelay.
1026
- const embeddedDelayMatch = raw.match(/quotaresetdelay"\s*:\s*"([^"]+)"/i);
1027
- if (embeddedDelayMatch && embeddedDelayMatch[1]) {
1028
- const parsed = parseDurationToMs(embeddedDelayMatch[1]);
1029
- if (parsed && parsed > 0) {
1030
- return parsed;
1031
- }
1032
- }
1033
- return null;
1034
- }
1035
- function parseDurationToMs(value) {
1036
- if (!value || typeof value !== 'string') {
1037
- return null;
1038
- }
1039
- const pattern = /(\d+(?:\.\d+)?)(ms|s|m|h)/gi;
1040
- let totalMs = 0;
1041
- let matched = false;
1042
- let match;
1043
- while ((match = pattern.exec(value)) !== null) {
1044
- matched = true;
1045
- const amount = Number.parseFloat(match[1]);
1046
- if (!Number.isFinite(amount)) {
1047
- continue;
1048
- }
1049
- const unit = match[2].toLowerCase();
1050
- if (unit === 'ms') {
1051
- totalMs += amount;
1052
- }
1053
- else if (unit === 'h') {
1054
- totalMs += amount * 3_600_000;
1055
- }
1056
- else if (unit === 'm') {
1057
- totalMs += amount * 60_000;
1058
- }
1059
- else if (unit === 's') {
1060
- totalMs += amount * 1_000;
1061
- }
1062
- }
1063
- if (!matched) {
1064
- return null;
1065
- }
1066
- if (totalMs <= 0) {
1067
- return null;
1068
- }
1069
- return Math.round(totalMs);
1070
- }
1071
- function normalizeLoadedQuotaState(providerKey, state) {
1072
- const key = typeof providerKey === 'string' && providerKey.trim() ? providerKey.trim() : state.providerKey;
1073
- const rawAuth = typeof state.authType === 'string'
1074
- ? String(state.authType).trim().toLowerCase()
1075
- : '';
1076
- const authType = rawAuth === 'apikey' ? 'apikey' : rawAuth === 'oauth' ? 'oauth' : 'unknown';
1077
- return {
1078
- ...state,
1079
- providerKey: key,
1080
- authType
1081
- };
1082
- }
1083
- function readPositiveNumberFromEnv(name, fallback) {
1084
- const raw = process.env[name];
1085
- if (!raw) {
1086
- return fallback;
1087
- }
1088
- const parsed = Number(raw);
1089
- if (!Number.isFinite(parsed) || parsed <= 0) {
1090
- return fallback;
1091
- }
1092
- return parsed;
1093
- }
1
+ export { QuotaManagerModule } from './antigravity-quota-manager.js';
2
+ export { ProviderQuotaDaemonModule } from './provider-quota-daemon.js';
1094
3
  //# sourceMappingURL=index.js.map