@jsonstudio/rcc 0.89.1968 → 0.89.2195

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 (555) hide show
  1. package/README.md +22 -0
  2. package/config/file-line-limit-policy.json +21 -0
  3. package/configsamples/config.json +1 -1
  4. package/configsamples/config.v1.quickstart.sanitized.json +6 -6
  5. package/configsamples/provider/iflow/config.v1.json +1 -1
  6. package/dist/app/config-readers.d.ts +18 -0
  7. package/dist/app/config-readers.js +95 -0
  8. package/dist/app/config-readers.js.map +1 -0
  9. package/dist/app/index.d.ts +9 -0
  10. package/dist/app/index.js +8 -0
  11. package/dist/app/index.js.map +1 -0
  12. package/dist/app/shutdown.d.ts +32 -0
  13. package/dist/app/shutdown.js +41 -0
  14. package/dist/app/shutdown.js.map +1 -0
  15. package/dist/bootstrap/index.d.ts +7 -0
  16. package/dist/bootstrap/index.js +7 -0
  17. package/dist/bootstrap/index.js.map +1 -0
  18. package/dist/bootstrap/log-filter.d.ts +15 -0
  19. package/dist/bootstrap/log-filter.js +81 -0
  20. package/dist/bootstrap/log-filter.js.map +1 -0
  21. package/dist/build-info.js +2 -2
  22. package/dist/cli/commands/claude.js +6 -5
  23. package/dist/cli/commands/claude.js.map +1 -1
  24. package/dist/cli/commands/init/basic.d.ts +1 -7
  25. package/dist/cli/commands/init/basic.js +0 -79
  26. package/dist/cli/commands/init/basic.js.map +1 -1
  27. package/dist/cli/commands/init/prompt-utils.d.ts +7 -0
  28. package/dist/cli/commands/init/prompt-utils.js +80 -0
  29. package/dist/cli/commands/init/prompt-utils.js.map +1 -0
  30. package/dist/cli/commands/init/workflows.js +2 -1
  31. package/dist/cli/commands/init/workflows.js.map +1 -1
  32. package/dist/cli/commands/init.js +74 -2
  33. package/dist/cli/commands/init.js.map +1 -1
  34. package/dist/cli/commands/launcher/index.d.ts +7 -0
  35. package/dist/cli/commands/launcher/index.js +7 -0
  36. package/dist/cli/commands/launcher/index.js.map +1 -0
  37. package/dist/cli/commands/launcher/types.d.ts +112 -0
  38. package/dist/cli/commands/launcher/types.js +7 -0
  39. package/dist/cli/commands/launcher/types.js.map +1 -0
  40. package/dist/cli/commands/launcher/utils.d.ts +114 -0
  41. package/dist/cli/commands/launcher/utils.js +378 -0
  42. package/dist/cli/commands/launcher/utils.js.map +1 -0
  43. package/dist/cli/commands/launcher-kernel.d.ts +2 -74
  44. package/dist/cli/commands/launcher-kernel.js +141 -143
  45. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  46. package/dist/cli/commands/start-types.d.ts +67 -0
  47. package/dist/cli/commands/start-types.js +3 -0
  48. package/dist/cli/commands/start-types.js.map +1 -0
  49. package/dist/cli/commands/start-utils.d.ts +19 -0
  50. package/dist/cli/commands/start-utils.js +78 -0
  51. package/dist/cli/commands/start-utils.js.map +1 -0
  52. package/dist/cli/commands/start.d.ts +2 -67
  53. package/dist/cli/commands/start.js +131 -47
  54. package/dist/cli/commands/start.js.map +1 -1
  55. package/dist/cli/commands/stop.js +13 -0
  56. package/dist/cli/commands/stop.js.map +1 -1
  57. package/dist/cli/commands/tmux-inject.js +1 -1
  58. package/dist/cli/commands/tmux-inject.js.map +1 -1
  59. package/dist/cli/config/init-config.js +60 -2
  60. package/dist/cli/config/init-config.js.map +1 -1
  61. package/dist/cli/config/init-provider-catalog.js +3 -1
  62. package/dist/cli/config/init-provider-catalog.js.map +1 -1
  63. package/dist/cli/config/precommand-default-script.d.ts +17 -0
  64. package/dist/cli/config/precommand-default-script.js +47 -0
  65. package/dist/cli/config/precommand-default-script.js.map +1 -0
  66. package/dist/cli.js +2 -0
  67. package/dist/cli.js.map +1 -1
  68. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +55 -5
  69. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  70. package/dist/commands/oauth.js +28 -4
  71. package/dist/commands/oauth.js.map +1 -1
  72. package/dist/commands/quota-daemon.js +12 -0
  73. package/dist/commands/quota-daemon.js.map +1 -1
  74. package/dist/configsamples/config.v1.quickstart.sanitized.json +6 -6
  75. package/dist/constants/index.d.ts +34 -0
  76. package/dist/constants/index.js +57 -0
  77. package/dist/constants/index.js.map +1 -1
  78. package/dist/docs/daemon-admin-ui.html +268 -11
  79. package/dist/error-handling/quiet-error-handling-center.js +19 -1
  80. package/dist/error-handling/quiet-error-handling-center.js.map +1 -1
  81. package/dist/index.js +233 -30
  82. package/dist/index.js.map +1 -1
  83. package/dist/manager/index.js +4 -4
  84. package/dist/manager/index.js.map +1 -1
  85. package/dist/manager/modules/quota/antigravity-quota-core.d.ts +26 -0
  86. package/dist/manager/modules/quota/antigravity-quota-core.js +23 -0
  87. package/dist/manager/modules/quota/antigravity-quota-core.js.map +1 -0
  88. package/dist/manager/modules/quota/antigravity-quota-helpers.d.ts +16 -0
  89. package/dist/manager/modules/quota/antigravity-quota-helpers.js +167 -0
  90. package/dist/manager/modules/quota/antigravity-quota-helpers.js.map +1 -0
  91. package/dist/manager/modules/quota/antigravity-quota-manager.d.ts +10 -39
  92. package/dist/manager/modules/quota/antigravity-quota-manager.js +167 -464
  93. package/dist/manager/modules/quota/antigravity-quota-manager.js.map +1 -1
  94. package/dist/manager/modules/quota/antigravity-quota-persistence.d.ts +20 -0
  95. package/dist/manager/modules/quota/antigravity-quota-persistence.js +139 -0
  96. package/dist/manager/modules/quota/antigravity-quota-persistence.js.map +1 -0
  97. package/dist/manager/modules/quota/antigravity-quota-runtime.d.ts +55 -0
  98. package/dist/manager/modules/quota/antigravity-quota-runtime.js +174 -0
  99. package/dist/manager/modules/quota/antigravity-quota-runtime.js.map +1 -0
  100. package/dist/manager/modules/quota/antigravity-quota-sync.d.ts +46 -0
  101. package/dist/manager/modules/quota/antigravity-quota-sync.js +162 -0
  102. package/dist/manager/modules/quota/antigravity-quota-sync.js.map +1 -0
  103. package/dist/manager/modules/quota/index.d.ts +1 -0
  104. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.d.ts +13 -0
  105. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js +149 -0
  106. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js.map +1 -0
  107. package/dist/manager/modules/quota/provider-quota-daemon.events.js +1 -148
  108. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  109. package/dist/manager/modules/quota/provider-quota-daemon.js.map +1 -1
  110. package/dist/manager/modules/quota/quota-adapter.d.ts +111 -0
  111. package/dist/manager/modules/quota/quota-adapter.js +325 -0
  112. package/dist/manager/modules/quota/quota-adapter.js.map +1 -0
  113. package/dist/manager/quota/provider-quota-center.d.ts +10 -0
  114. package/dist/manager/quota/provider-quota-center.js +15 -2
  115. package/dist/manager/quota/provider-quota-center.js.map +1 -1
  116. package/dist/modules/llmswitch/bridge/antigravity-signature.d.ts +28 -0
  117. package/dist/modules/llmswitch/bridge/antigravity-signature.js +180 -0
  118. package/dist/modules/llmswitch/bridge/antigravity-signature.js.map +1 -0
  119. package/dist/modules/llmswitch/bridge/index.d.ts +13 -0
  120. package/dist/modules/llmswitch/bridge/index.js +14 -0
  121. package/dist/modules/llmswitch/bridge/index.js.map +1 -0
  122. package/dist/modules/llmswitch/bridge/module-loader.d.ts +16 -0
  123. package/dist/modules/llmswitch/bridge/module-loader.js +59 -0
  124. package/dist/modules/llmswitch/bridge/module-loader.js.map +1 -0
  125. package/dist/modules/llmswitch/bridge/quota-manager.d.ts +8 -0
  126. package/dist/modules/llmswitch/bridge/quota-manager.js +37 -0
  127. package/dist/modules/llmswitch/bridge/quota-manager.js.map +1 -0
  128. package/dist/modules/llmswitch/bridge/response-converter.d.ts +11 -0
  129. package/dist/modules/llmswitch/bridge/response-converter.js +68 -0
  130. package/dist/modules/llmswitch/bridge/response-converter.js.map +1 -0
  131. package/dist/modules/llmswitch/bridge/routing-integrations.d.ts +12 -0
  132. package/dist/modules/llmswitch/bridge/routing-integrations.js +56 -0
  133. package/dist/modules/llmswitch/bridge/routing-integrations.js.map +1 -0
  134. package/dist/modules/llmswitch/bridge/runtime-integrations.d.ts +34 -0
  135. package/dist/modules/llmswitch/bridge/runtime-integrations.js +87 -0
  136. package/dist/modules/llmswitch/bridge/runtime-integrations.js.map +1 -0
  137. package/dist/modules/llmswitch/bridge/snapshot-recorder.d.ts +13 -0
  138. package/dist/modules/llmswitch/bridge/snapshot-recorder.js +484 -0
  139. package/dist/modules/llmswitch/bridge/snapshot-recorder.js.map +1 -0
  140. package/dist/modules/llmswitch/bridge/state-integrations.d.ts +59 -0
  141. package/dist/modules/llmswitch/bridge/state-integrations.js +264 -0
  142. package/dist/modules/llmswitch/bridge/state-integrations.js.map +1 -0
  143. package/dist/modules/llmswitch/bridge.d.ts +14 -131
  144. package/dist/modules/llmswitch/bridge.js +14 -834
  145. package/dist/modules/llmswitch/bridge.js.map +1 -1
  146. package/dist/modules/pipeline/types/provider-config-types.d.ts +240 -0
  147. package/dist/modules/pipeline/types/provider-config-types.js +2 -0
  148. package/dist/modules/pipeline/types/provider-config-types.js.map +1 -0
  149. package/dist/modules/pipeline/types/provider-types.d.ts +2 -239
  150. package/dist/modules/pipeline/types/provider-types.js +1 -1
  151. package/dist/modules/pipeline/types/provider-types.js.map +1 -1
  152. package/dist/modules/pipeline/utils/debug-logger.js +3 -5
  153. package/dist/modules/pipeline/utils/debug-logger.js.map +1 -1
  154. package/dist/providers/auth/apikey-auth.d.ts +57 -1
  155. package/dist/providers/auth/apikey-auth.js +131 -1
  156. package/dist/providers/auth/apikey-auth.js.map +1 -1
  157. package/dist/providers/auth/oauth-lifecycle/error-detection.d.ts +8 -0
  158. package/dist/providers/auth/oauth-lifecycle/error-detection.js +71 -0
  159. package/dist/providers/auth/oauth-lifecycle/error-detection.js.map +1 -0
  160. package/dist/providers/auth/oauth-lifecycle/index.d.ts +10 -0
  161. package/dist/providers/auth/oauth-lifecycle/index.js +11 -0
  162. package/dist/providers/auth/oauth-lifecycle/index.js.map +1 -0
  163. package/dist/providers/auth/oauth-lifecycle/path-resolver.d.ts +18 -0
  164. package/dist/providers/auth/oauth-lifecycle/path-resolver.js +121 -0
  165. package/dist/providers/auth/oauth-lifecycle/path-resolver.js.map +1 -0
  166. package/dist/providers/auth/oauth-lifecycle/throttle.d.ts +22 -0
  167. package/dist/providers/auth/oauth-lifecycle/throttle.js +37 -0
  168. package/dist/providers/auth/oauth-lifecycle/throttle.js.map +1 -0
  169. package/dist/providers/auth/oauth-lifecycle/token-helpers.d.ts +36 -0
  170. package/dist/providers/auth/oauth-lifecycle/token-helpers.js +127 -0
  171. package/dist/providers/auth/oauth-lifecycle/token-helpers.js.map +1 -0
  172. package/dist/providers/auth/oauth-lifecycle/token-io.d.ts +14 -0
  173. package/dist/providers/auth/oauth-lifecycle/token-io.js +151 -0
  174. package/dist/providers/auth/oauth-lifecycle/token-io.js.map +1 -0
  175. package/dist/providers/auth/oauth-lifecycle.js +70 -446
  176. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  177. package/dist/providers/auth/oauth-repair-cooldown.js +8 -3
  178. package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
  179. package/dist/providers/auth/oauth-repair-env.js +5 -3
  180. package/dist/providers/auth/oauth-repair-env.js.map +1 -1
  181. package/dist/providers/auth/oauth-token-utils.d.ts +61 -0
  182. package/dist/providers/auth/oauth-token-utils.js +187 -0
  183. package/dist/providers/auth/oauth-token-utils.js.map +1 -0
  184. package/dist/providers/auth/oauth-utils/camoufox-helper.d.ts +21 -0
  185. package/dist/providers/auth/oauth-utils/camoufox-helper.js +82 -0
  186. package/dist/providers/auth/oauth-utils/camoufox-helper.js.map +1 -0
  187. package/dist/providers/auth/oauth-utils/error-extraction.d.ts +17 -0
  188. package/dist/providers/auth/oauth-utils/error-extraction.js +80 -0
  189. package/dist/providers/auth/oauth-utils/error-extraction.js.map +1 -0
  190. package/dist/providers/auth/oauth-utils/index.d.ts +7 -0
  191. package/dist/providers/auth/oauth-utils/index.js +8 -0
  192. package/dist/providers/auth/oauth-utils/index.js.map +1 -0
  193. package/dist/providers/auth/token-refresh/index.d.ts +6 -0
  194. package/dist/providers/auth/token-refresh/index.js +7 -0
  195. package/dist/providers/auth/token-refresh/index.js.map +1 -0
  196. package/dist/providers/auth/token-refresh/token-state.d.ts +17 -0
  197. package/dist/providers/auth/token-refresh/token-state.js +30 -0
  198. package/dist/providers/auth/token-refresh/token-state.js.map +1 -0
  199. package/dist/providers/auth/token-storage/index.d.ts +7 -0
  200. package/dist/providers/auth/token-storage/index.js +8 -0
  201. package/dist/providers/auth/token-storage/index.js.map +1 -0
  202. package/dist/providers/auth/token-storage/token-file-resolver.d.ts +12 -0
  203. package/dist/providers/auth/token-storage/token-file-resolver.js +117 -0
  204. package/dist/providers/auth/token-storage/token-file-resolver.js.map +1 -0
  205. package/dist/providers/auth/token-storage/token-persistence.d.ts +22 -0
  206. package/dist/providers/auth/token-storage/token-persistence.js +86 -0
  207. package/dist/providers/auth/token-storage/token-persistence.js.map +1 -0
  208. package/dist/providers/auth/tokenfile-auth.js +12 -9
  209. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  210. package/dist/providers/core/api/provider-config.d.ts +8 -0
  211. package/dist/providers/core/config/camoufox-launcher.d.ts +1 -0
  212. package/dist/providers/core/config/camoufox-launcher.js +40 -9
  213. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  214. package/dist/providers/core/config/oauth-flows.js +7 -2
  215. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  216. package/dist/providers/core/config/provider-debug-hooks.d.ts +0 -12
  217. package/dist/providers/core/config/provider-debug-hooks.js +16 -56
  218. package/dist/providers/core/config/provider-debug-hooks.js.map +1 -1
  219. package/dist/providers/core/config/provider-debug-output-utils.d.ts +38 -0
  220. package/dist/providers/core/config/provider-debug-output-utils.js +46 -0
  221. package/dist/providers/core/config/provider-debug-output-utils.js.map +1 -0
  222. package/dist/providers/core/config/provider-oauth-configs.js +13 -9
  223. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  224. package/dist/providers/core/runtime/antigravity-request-type.d.ts +2 -0
  225. package/dist/providers/core/runtime/antigravity-request-type.js +126 -0
  226. package/dist/providers/core/runtime/antigravity-request-type.js.map +1 -0
  227. package/dist/providers/core/runtime/base-provider-runtime-helpers.d.ts +27 -0
  228. package/dist/providers/core/runtime/base-provider-runtime-helpers.js +105 -0
  229. package/dist/providers/core/runtime/base-provider-runtime-helpers.js.map +1 -0
  230. package/dist/providers/core/runtime/base-provider-series-cooldown.d.ts +19 -0
  231. package/dist/providers/core/runtime/base-provider-series-cooldown.js +363 -0
  232. package/dist/providers/core/runtime/base-provider-series-cooldown.js.map +1 -0
  233. package/dist/providers/core/runtime/base-provider.d.ts +4 -35
  234. package/dist/providers/core/runtime/base-provider.js +20 -501
  235. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  236. package/dist/providers/core/runtime/deepseek-http-provider-helpers.d.ts +32 -0
  237. package/dist/providers/core/runtime/deepseek-http-provider-helpers.js +301 -0
  238. package/dist/providers/core/runtime/deepseek-http-provider-helpers.js.map +1 -0
  239. package/dist/providers/core/runtime/deepseek-http-provider.d.ts +0 -3
  240. package/dist/providers/core/runtime/deepseek-http-provider.js +5 -127
  241. package/dist/providers/core/runtime/deepseek-http-provider.js.map +1 -1
  242. package/dist/providers/core/runtime/deepseek-session-pow-helpers.d.ts +21 -0
  243. package/dist/providers/core/runtime/deepseek-session-pow-helpers.js +99 -0
  244. package/dist/providers/core/runtime/deepseek-session-pow-helpers.js.map +1 -0
  245. package/dist/providers/core/runtime/deepseek-session-pow.js +13 -108
  246. package/dist/providers/core/runtime/deepseek-session-pow.js.map +1 -1
  247. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +0 -15
  248. package/dist/providers/core/runtime/gemini-cli-http-provider.js +13 -303
  249. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  250. package/dist/providers/core/runtime/gemini-cli-response-postprocessor.d.ts +9 -0
  251. package/dist/providers/core/runtime/gemini-cli-response-postprocessor.js +85 -0
  252. package/dist/providers/core/runtime/gemini-cli-response-postprocessor.js.map +1 -0
  253. package/dist/providers/core/runtime/gemini-http-provider.js +2 -2
  254. package/dist/providers/core/runtime/gemini-http-provider.js.map +1 -1
  255. package/dist/providers/core/runtime/gemini-sse-normalizer.d.ts +25 -0
  256. package/dist/providers/core/runtime/gemini-sse-normalizer.js +159 -0
  257. package/dist/providers/core/runtime/gemini-sse-normalizer.js.map +1 -0
  258. package/dist/providers/core/runtime/http-request-executor.js +1 -48
  259. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  260. package/dist/providers/core/runtime/http-transport-provider.d.ts +2 -48
  261. package/dist/providers/core/runtime/http-transport-provider.js +158 -1273
  262. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  263. package/dist/providers/core/runtime/provider-bootstrap-utils.d.ts +20 -0
  264. package/dist/providers/core/runtime/provider-bootstrap-utils.js +78 -0
  265. package/dist/providers/core/runtime/provider-bootstrap-utils.js.map +1 -0
  266. package/dist/providers/core/runtime/provider-factory-helpers.d.ts +19 -0
  267. package/dist/providers/core/runtime/provider-factory-helpers.js +203 -0
  268. package/dist/providers/core/runtime/provider-factory-helpers.js.map +1 -0
  269. package/dist/providers/core/runtime/provider-factory.d.ts +9 -7
  270. package/dist/providers/core/runtime/provider-factory.js +57 -214
  271. package/dist/providers/core/runtime/provider-factory.js.map +1 -1
  272. package/dist/providers/core/runtime/provider-family-profile-utils.d.ts +23 -0
  273. package/dist/providers/core/runtime/provider-family-profile-utils.js +57 -0
  274. package/dist/providers/core/runtime/provider-family-profile-utils.js.map +1 -0
  275. package/dist/providers/core/runtime/provider-http-executor-utils.d.ts +32 -0
  276. package/dist/providers/core/runtime/provider-http-executor-utils.js +92 -0
  277. package/dist/providers/core/runtime/provider-http-executor-utils.js.map +1 -0
  278. package/dist/providers/core/runtime/provider-iflow-business-error-utils.d.ts +15 -0
  279. package/dist/providers/core/runtime/provider-iflow-business-error-utils.js +49 -0
  280. package/dist/providers/core/runtime/provider-iflow-business-error-utils.js.map +1 -0
  281. package/dist/providers/core/runtime/provider-request-executor-deps-factory.d.ts +29 -0
  282. package/dist/providers/core/runtime/provider-request-executor-deps-factory.js +41 -0
  283. package/dist/providers/core/runtime/provider-request-executor-deps-factory.js.map +1 -0
  284. package/dist/providers/core/runtime/provider-request-header-orchestrator.d.ts +30 -0
  285. package/dist/providers/core/runtime/provider-request-header-orchestrator.js +91 -0
  286. package/dist/providers/core/runtime/provider-request-header-orchestrator.js.map +1 -0
  287. package/dist/providers/core/runtime/provider-request-preprocessor.d.ts +5 -0
  288. package/dist/providers/core/runtime/provider-request-preprocessor.js +39 -0
  289. package/dist/providers/core/runtime/provider-request-preprocessor.js.map +1 -0
  290. package/dist/providers/core/runtime/provider-request-shaping-utils.d.ts +37 -0
  291. package/dist/providers/core/runtime/provider-request-shaping-utils.js +65 -0
  292. package/dist/providers/core/runtime/provider-request-shaping-utils.js.map +1 -0
  293. package/dist/providers/core/runtime/provider-response-postprocessor.d.ts +7 -0
  294. package/dist/providers/core/runtime/provider-response-postprocessor.js +28 -0
  295. package/dist/providers/core/runtime/provider-response-postprocessor.js.map +1 -0
  296. package/dist/providers/core/runtime/provider-runtime-utils.d.ts +16 -0
  297. package/dist/providers/core/runtime/provider-runtime-utils.js +51 -0
  298. package/dist/providers/core/runtime/provider-runtime-utils.js.map +1 -0
  299. package/dist/providers/core/runtime/responses-provider-helpers.d.ts +37 -0
  300. package/dist/providers/core/runtime/responses-provider-helpers.js +212 -0
  301. package/dist/providers/core/runtime/responses-provider-helpers.js.map +1 -0
  302. package/dist/providers/core/runtime/responses-provider.d.ts +0 -10
  303. package/dist/providers/core/runtime/responses-provider.js +14 -224
  304. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  305. package/dist/providers/core/runtime/runtime-endpoint-resolver.d.ts +21 -0
  306. package/dist/providers/core/runtime/runtime-endpoint-resolver.js +64 -0
  307. package/dist/providers/core/runtime/runtime-endpoint-resolver.js.map +1 -0
  308. package/dist/providers/core/runtime/service-profile-resolver.d.ts +29 -0
  309. package/dist/providers/core/runtime/service-profile-resolver.js +88 -0
  310. package/dist/providers/core/runtime/service-profile-resolver.js.map +1 -0
  311. package/dist/providers/core/runtime/transport/auth-mode-utils.d.ts +13 -0
  312. package/dist/providers/core/runtime/transport/auth-mode-utils.js +43 -0
  313. package/dist/providers/core/runtime/transport/auth-mode-utils.js.map +1 -0
  314. package/dist/providers/core/runtime/transport/auth-provider-factory.d.ts +37 -0
  315. package/dist/providers/core/runtime/transport/auth-provider-factory.js +131 -0
  316. package/dist/providers/core/runtime/transport/auth-provider-factory.js.map +1 -0
  317. package/dist/providers/core/runtime/transport/header-utils.d.ts +15 -0
  318. package/dist/providers/core/runtime/transport/header-utils.js +85 -0
  319. package/dist/providers/core/runtime/transport/header-utils.js.map +1 -0
  320. package/dist/providers/core/runtime/transport/iflow-signer.d.ts +12 -0
  321. package/dist/providers/core/runtime/transport/iflow-signer.js +63 -0
  322. package/dist/providers/core/runtime/transport/iflow-signer.js.map +1 -0
  323. package/dist/providers/core/runtime/transport/index.d.ts +15 -0
  324. package/dist/providers/core/runtime/transport/index.js +16 -0
  325. package/dist/providers/core/runtime/transport/index.js.map +1 -0
  326. package/dist/providers/core/runtime/transport/oauth-header-preflight.d.ts +12 -0
  327. package/dist/providers/core/runtime/transport/oauth-header-preflight.js +56 -0
  328. package/dist/providers/core/runtime/transport/oauth-header-preflight.js.map +1 -0
  329. package/dist/providers/core/runtime/transport/oauth-recovery-handler.d.ts +34 -0
  330. package/dist/providers/core/runtime/transport/oauth-recovery-handler.js +126 -0
  331. package/dist/providers/core/runtime/transport/oauth-recovery-handler.js.map +1 -0
  332. package/dist/providers/core/runtime/transport/provider-payload-utils.d.ts +21 -0
  333. package/dist/providers/core/runtime/transport/provider-payload-utils.js +88 -0
  334. package/dist/providers/core/runtime/transport/provider-payload-utils.js.map +1 -0
  335. package/dist/providers/core/runtime/transport/request-header-builder.d.ts +24 -0
  336. package/dist/providers/core/runtime/transport/request-header-builder.js +90 -0
  337. package/dist/providers/core/runtime/transport/request-header-builder.js.map +1 -0
  338. package/dist/providers/core/runtime/transport/runtime-detector.d.ts +22 -0
  339. package/dist/providers/core/runtime/transport/runtime-detector.js +63 -0
  340. package/dist/providers/core/runtime/transport/runtime-detector.js.map +1 -0
  341. package/dist/providers/core/runtime/transport/session-header-utils.d.ts +8 -0
  342. package/dist/providers/core/runtime/transport/session-header-utils.js +72 -0
  343. package/dist/providers/core/runtime/transport/session-header-utils.js.map +1 -0
  344. package/dist/providers/core/runtime/vision-debug-utils.d.ts +2 -0
  345. package/dist/providers/core/runtime/vision-debug-utils.js +31 -0
  346. package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -1
  347. package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +13 -0
  348. package/dist/providers/core/strategies/oauth-auth-code-flow.js +272 -55
  349. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  350. package/dist/providers/core/utils/snapshot-writer-buffer.d.ts +13 -0
  351. package/dist/providers/core/utils/snapshot-writer-buffer.js +39 -0
  352. package/dist/providers/core/utils/snapshot-writer-buffer.js.map +1 -0
  353. package/dist/providers/core/utils/snapshot-writer.js +75 -54
  354. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  355. package/dist/providers/profile/families/antigravity-profile.js +2 -10
  356. package/dist/providers/profile/families/antigravity-profile.js.map +1 -1
  357. package/dist/providers/profile/families/deepseek-profile.d.ts +2 -0
  358. package/dist/providers/profile/families/deepseek-profile.js +110 -0
  359. package/dist/providers/profile/families/deepseek-profile.js.map +1 -0
  360. package/dist/providers/profile/families/iflow-profile.js +89 -10
  361. package/dist/providers/profile/families/iflow-profile.js.map +1 -1
  362. package/dist/providers/profile/provider-profile-loader.d.ts +5 -0
  363. package/dist/providers/profile/provider-profile-loader.js +35 -0
  364. package/dist/providers/profile/provider-profile-loader.js.map +1 -1
  365. package/dist/providers/profile/provider-profile.d.ts +16 -1
  366. package/dist/runtime/runtime-flags.js +1 -1
  367. package/dist/runtime/runtime-flags.js.map +1 -1
  368. package/dist/runtime/wasm-runtime/index.d.ts +56 -0
  369. package/dist/runtime/wasm-runtime/index.js +69 -0
  370. package/dist/runtime/wasm-runtime/index.js.map +1 -0
  371. package/dist/scripts/camoufox/launch-auth.mjs +158 -10
  372. package/dist/server/handlers/handler-response-utils.d.ts +13 -0
  373. package/dist/server/handlers/handler-response-utils.js +427 -0
  374. package/dist/server/handlers/handler-response-utils.js.map +1 -0
  375. package/dist/server/handlers/handler-utils.d.ts +2 -11
  376. package/dist/server/handlers/handler-utils.js +21 -421
  377. package/dist/server/handlers/handler-utils.js.map +1 -1
  378. package/dist/server/runtime/http-server/clock-client-reaper.d.ts +23 -0
  379. package/dist/server/runtime/http-server/clock-client-reaper.js +159 -0
  380. package/dist/server/runtime/http-server/clock-client-reaper.js.map +1 -0
  381. package/dist/server/runtime/http-server/clock-client-registry-utils.d.ts +87 -0
  382. package/dist/server/runtime/http-server/clock-client-registry-utils.js +276 -0
  383. package/dist/server/runtime/http-server/clock-client-registry-utils.js.map +1 -0
  384. package/dist/server/runtime/http-server/clock-client-registry.d.ts +5 -50
  385. package/dist/server/runtime/http-server/clock-client-registry.js +74 -320
  386. package/dist/server/runtime/http-server/clock-client-registry.js.map +1 -1
  387. package/dist/server/runtime/http-server/clock-client-route-utils.d.ts +12 -0
  388. package/dist/server/runtime/http-server/clock-client-route-utils.js +210 -0
  389. package/dist/server/runtime/http-server/clock-client-route-utils.js.map +1 -0
  390. package/dist/server/runtime/http-server/clock-client-routes.js +27 -201
  391. package/dist/server/runtime/http-server/clock-client-routes.js.map +1 -1
  392. package/dist/server/runtime/http-server/clock-daemon-log-throttle.d.ts +12 -0
  393. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js +56 -0
  394. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js.map +1 -0
  395. package/dist/server/runtime/http-server/daemon-admin/control-handler.js +143 -14
  396. package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
  397. package/dist/server/runtime/http-server/daemon-admin/credentials-handler-utils.d.ts +19 -0
  398. package/dist/server/runtime/http-server/daemon-admin/credentials-handler-utils.js +107 -0
  399. package/dist/server/runtime/http-server/daemon-admin/credentials-handler-utils.js.map +1 -0
  400. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +2 -104
  401. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  402. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.d.ts +28 -0
  403. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js +298 -0
  404. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js.map +1 -0
  405. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.d.ts +22 -0
  406. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js +211 -0
  407. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js.map +1 -0
  408. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +25 -454
  409. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  410. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +81 -32
  411. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
  412. package/dist/server/runtime/http-server/daemon-admin/routecodex-x7e-gate.d.ts +22 -0
  413. package/dist/server/runtime/http-server/daemon-admin/routecodex-x7e-gate.js +70 -0
  414. package/dist/server/runtime/http-server/daemon-admin/routecodex-x7e-gate.js.map +1 -0
  415. package/dist/server/runtime/http-server/executor/antigravity-detector.d.ts +34 -0
  416. package/dist/server/runtime/http-server/executor/antigravity-detector.js +118 -0
  417. package/dist/server/runtime/http-server/executor/antigravity-detector.js.map +1 -0
  418. package/dist/server/runtime/http-server/executor/env-config.d.ts +13 -0
  419. package/dist/server/runtime/http-server/executor/env-config.js +20 -0
  420. package/dist/server/runtime/http-server/executor/env-config.js.map +1 -0
  421. package/dist/server/runtime/http-server/executor/index.d.ts +11 -0
  422. package/dist/server/runtime/http-server/executor/index.js +18 -0
  423. package/dist/server/runtime/http-server/executor/index.js.map +1 -0
  424. package/dist/server/runtime/http-server/executor/provider-request-context.d.ts +20 -0
  425. package/dist/server/runtime/http-server/executor/provider-request-context.js +38 -0
  426. package/dist/server/runtime/http-server/executor/provider-request-context.js.map +1 -0
  427. package/dist/server/runtime/http-server/executor/provider-response-converter.d.ts +23 -0
  428. package/dist/server/runtime/http-server/executor/provider-response-converter.js +337 -0
  429. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -0
  430. package/dist/server/runtime/http-server/executor/provider-response-utils.d.ts +8 -0
  431. package/dist/server/runtime/http-server/executor/provider-response-utils.js +93 -0
  432. package/dist/server/runtime/http-server/executor/provider-response-utils.js.map +1 -0
  433. package/dist/server/runtime/http-server/executor/provider-runtime-resolver.d.ts +23 -0
  434. package/dist/server/runtime/http-server/executor/provider-runtime-resolver.js +85 -0
  435. package/dist/server/runtime/http-server/executor/provider-runtime-resolver.js.map +1 -0
  436. package/dist/server/runtime/http-server/executor/request-executor-core-utils.d.ts +7 -0
  437. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js +39 -0
  438. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js.map +1 -0
  439. package/dist/server/runtime/http-server/executor/request-retry-helpers.d.ts +16 -0
  440. package/dist/server/runtime/http-server/executor/request-retry-helpers.js +218 -0
  441. package/dist/server/runtime/http-server/executor/request-retry-helpers.js.map +1 -0
  442. package/dist/server/runtime/http-server/executor/retry-engine.d.ts +21 -0
  443. package/dist/server/runtime/http-server/executor/retry-engine.js +73 -0
  444. package/dist/server/runtime/http-server/executor/retry-engine.js.map +1 -0
  445. package/dist/server/runtime/http-server/executor/sse-error-handler.d.ts +14 -0
  446. package/dist/server/runtime/http-server/executor/sse-error-handler.js +127 -0
  447. package/dist/server/runtime/http-server/executor/sse-error-handler.js.map +1 -0
  448. package/dist/server/runtime/http-server/executor/usage-aggregator.d.ts +39 -0
  449. package/dist/server/runtime/http-server/executor/usage-aggregator.js +177 -0
  450. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -0
  451. package/dist/server/runtime/http-server/executor/usage-logger.d.ts +7 -0
  452. package/dist/server/runtime/http-server/executor/usage-logger.js +13 -0
  453. package/dist/server/runtime/http-server/executor/usage-logger.js.map +1 -0
  454. package/dist/server/runtime/http-server/executor/utils.d.ts +21 -0
  455. package/dist/server/runtime/http-server/executor/utils.js +62 -0
  456. package/dist/server/runtime/http-server/executor/utils.js.map +1 -0
  457. package/dist/server/runtime/http-server/executor-metadata.js +83 -2
  458. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  459. package/dist/server/runtime/http-server/executor-response.js +17 -9
  460. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  461. package/dist/server/runtime/http-server/http-server-bootstrap.d.ts +31 -0
  462. package/dist/server/runtime/http-server/http-server-bootstrap.js +367 -0
  463. package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -0
  464. package/dist/server/runtime/http-server/http-server-clock-daemon.d.ts +5 -0
  465. package/dist/server/runtime/http-server/http-server-clock-daemon.js +242 -0
  466. package/dist/server/runtime/http-server/http-server-clock-daemon.js.map +1 -0
  467. package/dist/server/runtime/http-server/http-server-legacy-pipeline.d.ts +2 -0
  468. package/dist/server/runtime/http-server/http-server-legacy-pipeline.js +65 -0
  469. package/dist/server/runtime/http-server/http-server-legacy-pipeline.js.map +1 -0
  470. package/dist/server/runtime/http-server/http-server-lifecycle.d.ts +27 -0
  471. package/dist/server/runtime/http-server/http-server-lifecycle.js +285 -0
  472. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -0
  473. package/dist/server/runtime/http-server/http-server-runtime-providers.d.ts +10 -0
  474. package/dist/server/runtime/http-server/http-server-runtime-providers.js +415 -0
  475. package/dist/server/runtime/http-server/http-server-runtime-providers.js.map +1 -0
  476. package/dist/server/runtime/http-server/http-server-runtime-setup.d.ts +2 -0
  477. package/dist/server/runtime/http-server/http-server-runtime-setup.js +93 -0
  478. package/dist/server/runtime/http-server/http-server-runtime-setup.js.map +1 -0
  479. package/dist/server/runtime/http-server/index.d.ts +2 -46
  480. package/dist/server/runtime/http-server/index.js +96 -2615
  481. package/dist/server/runtime/http-server/index.js.map +1 -1
  482. package/dist/server/runtime/http-server/request-executor.d.ts +8 -21
  483. package/dist/server/runtime/http-server/request-executor.js +94 -956
  484. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  485. package/dist/server/runtime/http-server/routes.js +2 -2
  486. package/dist/server/runtime/http-server/routes.js.map +1 -1
  487. package/dist/server/runtime/http-server/servertool-admin-state.d.ts +42 -0
  488. package/dist/server/runtime/http-server/servertool-admin-state.js +210 -0
  489. package/dist/server/runtime/http-server/servertool-admin-state.js.map +1 -0
  490. package/dist/server/runtime/http-server/stats-manager-internals.d.ts +96 -0
  491. package/dist/server/runtime/http-server/stats-manager-internals.js +311 -0
  492. package/dist/server/runtime/http-server/stats-manager-internals.js.map +1 -0
  493. package/dist/server/runtime/http-server/stats-manager-table.d.ts +6 -0
  494. package/dist/server/runtime/http-server/stats-manager-table.js +135 -0
  495. package/dist/server/runtime/http-server/stats-manager-table.js.map +1 -0
  496. package/dist/server/runtime/http-server/stats-manager.d.ts +0 -23
  497. package/dist/server/runtime/http-server/stats-manager.js +95 -483
  498. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  499. package/dist/server/utils/client-connection-state.js +61 -0
  500. package/dist/server/utils/client-connection-state.js.map +1 -1
  501. package/dist/server/utils/request-id-manager.d.ts +6 -0
  502. package/dist/server/utils/request-id-manager.js +105 -15
  503. package/dist/server/utils/request-id-manager.js.map +1 -1
  504. package/dist/server/utils/stage-logger.js +14 -4
  505. package/dist/server/utils/stage-logger.js.map +1 -1
  506. package/dist/server-lifecycle/index.d.ts +6 -0
  507. package/dist/server-lifecycle/index.js +7 -0
  508. package/dist/server-lifecycle/index.js.map +1 -0
  509. package/dist/server-lifecycle/port-utils.d.ts +18 -0
  510. package/dist/server-lifecycle/port-utils.js +204 -0
  511. package/dist/server-lifecycle/port-utils.js.map +1 -0
  512. package/dist/sharedmodule/process-snapshot.d.ts +26 -0
  513. package/dist/sharedmodule/process-snapshot.js +141 -0
  514. package/dist/sharedmodule/process-snapshot.js.map +1 -0
  515. package/dist/token-daemon/index.js +10 -3
  516. package/dist/token-daemon/index.js.map +1 -1
  517. package/dist/token-daemon/token-daemon.js +9 -11
  518. package/dist/token-daemon/token-daemon.js.map +1 -1
  519. package/dist/token-daemon/token-utils.d.ts +1 -1
  520. package/dist/token-daemon/token-utils.js +19 -3
  521. package/dist/token-daemon/token-utils.js.map +1 -1
  522. package/dist/tools/semantic-replay-snapshot-loader.d.ts +4 -0
  523. package/dist/tools/semantic-replay-snapshot-loader.js +396 -0
  524. package/dist/tools/semantic-replay-snapshot-loader.js.map +1 -0
  525. package/dist/tools/semantic-replay.js +2 -393
  526. package/dist/tools/semantic-replay.js.map +1 -1
  527. package/dist/utils/daemon-stop-intent.d.ts +17 -0
  528. package/dist/utils/daemon-stop-intent.js +104 -0
  529. package/dist/utils/daemon-stop-intent.js.map +1 -0
  530. package/dist/utils/key-429-tracker.js +6 -5
  531. package/dist/utils/key-429-tracker.js.map +1 -1
  532. package/dist/utils/pipeline-health-manager.js +7 -6
  533. package/dist/utils/pipeline-health-manager.js.map +1 -1
  534. package/dist/utils/process-lifecycle-logger.js +45 -1
  535. package/dist/utils/process-lifecycle-logger.js.map +1 -1
  536. package/dist/utils/runtime-exit-forensics.d.ts +2 -2
  537. package/dist/utils/runtime-exit-forensics.js +10 -5
  538. package/dist/utils/runtime-exit-forensics.js.map +1 -1
  539. package/dist/utils/snapshot-writer.js +3 -3
  540. package/dist/utils/snapshot-writer.js.map +1 -1
  541. package/docs/QUOTA_MANAGER_V3.md +3 -0
  542. package/docs/VIRTUAL_ROUTER_PRIORITY_AND_HEALTH.md +114 -0
  543. package/docs/daemon-admin-ui.html +268 -11
  544. package/docs/file-line-limit-gate.md +30 -0
  545. package/docs/refactoring/host-164.3-responsibility-migration.md +62 -0
  546. package/docs/release-iflow-400-gate.md +58 -0
  547. package/docs/replay-evidence-iflow-400.txt +33 -0
  548. package/docs/stop-message-auto.md +4 -3
  549. package/package.json +4 -3
  550. package/scripts/auth-iflow-manual.mjs +13 -23
  551. package/scripts/camoufox/launch-auth.mjs +158 -10
  552. package/scripts/ci/check-file-line-limit.mjs +149 -0
  553. package/scripts/copy-compat-assets.mjs +26 -0
  554. package/scripts/tests/ci-jest.mjs +4 -0
  555. package/scripts/verify-codex-error-samples.mjs +27 -6
@@ -8,165 +8,29 @@
8
8
  * - 保持API兼容性
9
9
  */
10
10
  import express, {} from 'express';
11
- import * as fs from 'node:fs/promises';
12
- import path from 'node:path';
13
11
  import { ErrorHandlingCenter } from 'rcc-errorhandling';
14
- import { ProviderFactory } from '../../../providers/core/runtime/provider-factory.js';
15
12
  import { PipelineDebugLogger as PipelineDebugLoggerImpl } from '../../../modules/pipeline/utils/debug-logger.js';
16
- import { attachProviderRuntimeMetadata } from '../../../providers/core/runtime/provider-runtime-metadata.js';
17
- import { preloadAntigravityAliasUserAgents, primeAntigravityUserAgentVersion } from '../../../providers/auth/antigravity-user-agent.js';
18
- import { getAntigravityWarmupBlacklistDurationMs, isAntigravityWarmupEnabled, warmupCheckAntigravityAlias } from '../../../providers/auth/antigravity-warmup.js';
19
- import { shutdownCamoufoxLaunchers } from '../../../providers/core/config/camoufox-launcher.js';
20
13
  import { AuthFileResolver } from '../../../config/auth-file-resolver.js';
21
- import { buildProviderProfiles } from '../../../providers/profile/provider-profile-loader.js';
22
- import { isStageLoggingEnabled, logPipelineStage } from '../../utils/stage-logger.js';
14
+ import { isStageLoggingEnabled } from '../../utils/stage-logger.js';
23
15
  import { registerApiKeyAuthMiddleware, registerDefaultMiddleware } from './middleware.js';
24
- import { registerHttpRoutes, registerOAuthPortalRoute } from './routes.js';
25
- import { mapProviderProtocol, normalizeProviderType, resolveProviderIdentity, asRecord } from './provider-utils.js';
16
+ import { registerOAuthPortalRoute } from './routes.js';
26
17
  import { resolveRepoRoot } from './llmswitch-loader.js';
27
- import { enhanceProviderRequestId } from '../../utils/request-id-manager.js';
28
- import { convertProviderResponse as bridgeConvertProviderResponse, createSnapshotRecorder as bridgeCreateSnapshotRecorder, rebindResponsesConversationRequestId, extractSessionIdentifiersFromMetadata, bootstrapVirtualRouterConfig, getProviderSuccessCenter, getHubPipelineCtor, resolveClockConfigSnapshot, reserveClockDueTasks, commitClockDueReservation, clearClockTasksSnapshot } from '../../../modules/llmswitch/bridge.js';
29
- import { initializeRouteErrorHub, reportRouteError } from '../../../error-handling/route-error-hub.js';
30
- import { writeClientSnapshot } from '../../../providers/core/utils/snapshot-writer.js';
31
18
  import { createServerColoredLogger } from './colored-logger.js';
32
- import { formatValueForConsole } from '../../../utils/logger.js';
33
19
  import { QuietErrorHandlingCenter } from '../../../error-handling/quiet-error-handling-center.js';
34
- import { describeRetryReason, isNetworkTransportError, shouldRetryProviderError, waitBeforeRetry } from './executor-provider.js';
35
20
  import { ManagerDaemon } from '../../../manager/index.js';
36
- import { HealthManagerModule } from '../../../manager/modules/health/index.js';
37
- import { RoutingStateManagerModule } from '../../../manager/modules/routing/index.js';
38
- import { TokenManagerModule } from '../../../manager/modules/token/index.js';
39
21
  import { ensureServerScopedSessionDir } from './session-dir.js';
40
22
  import { canonicalizeServerId } from './server-id.js';
41
23
  import { StatsManager } from './stats-manager.js';
42
- import { loadRouteCodexConfig } from '../../../config/routecodex-config-loader.js';
43
- import { buildInfo } from '../../../build-info.js';
44
- import { getClockClientRegistry } from './clock-client-registry.js';
45
- import { toExactMatchClockConfig } from './clock-daemon-inject-config.js';
46
- import { isTmuxSessionAlive, killManagedTmuxSession } from './tmux-session-probe.js';
47
- import { terminateManagedClientProcess } from './managed-process-probe.js';
48
- import { recordHubShadowCompareDiff, resolveHubShadowCompareConfig, shouldRunHubShadowCompare } from './hub-shadow-compare.js';
49
- import { recordLlmsEngineShadowDiff, isLlmsEngineShadowEnabledForSubpath, resolveLlmsEngineShadowConfig, shouldRunLlmsEngineShadowForSubpath } from '../../../utils/llms-engine-shadow.js';
50
- import { resolveLlmswitchCoreVersion } from '../../../utils/runtime-versions.js';
51
- const DEFAULT_MAX_PROVIDER_ATTEMPTS = 6;
52
- const DEFAULT_ANTIGRAVITY_MAX_PROVIDER_ATTEMPTS = 20;
53
- const RETRYABLE_SSE_ERROR_CODE_HINTS = [
54
- 'internal_network_failure',
55
- 'network_error',
56
- 'api_connection_error',
57
- 'service_unavailable',
58
- 'internal_server_error',
59
- 'overloaded_error',
60
- 'rate_limit_error',
61
- 'request_timeout',
62
- 'timeout'
63
- ];
64
- const RETRYABLE_SSE_MESSAGE_HINTS = [
65
- 'internal network failure',
66
- 'network failure',
67
- 'network error',
68
- 'temporarily unavailable',
69
- 'temporarily unreachable',
70
- 'upstream disconnected',
71
- 'connection reset',
72
- 'connection closed',
73
- 'timed out',
74
- 'timeout'
75
- ];
76
- function firstNonEmptyString(candidates) {
77
- for (const candidate of candidates) {
78
- if (typeof candidate !== 'string') {
79
- continue;
80
- }
81
- const trimmed = candidate.trim();
82
- if (trimmed) {
83
- return trimmed;
84
- }
85
- }
86
- return undefined;
87
- }
88
- function firstFiniteNumber(candidates) {
89
- for (const candidate of candidates) {
90
- if (typeof candidate === 'number' && Number.isFinite(candidate)) {
91
- return candidate;
92
- }
93
- }
94
- return undefined;
95
- }
96
- function isRetryableSseWrapperError(message, errorCode, status) {
97
- if (typeof status === 'number' && Number.isFinite(status)) {
98
- if (status === 408 || status === 425 || status === 429 || status >= 500) {
99
- return true;
100
- }
101
- }
102
- const normalizedCode = typeof errorCode === 'string' ? errorCode.trim().toLowerCase() : '';
103
- if (normalizedCode && RETRYABLE_SSE_ERROR_CODE_HINTS.some((hint) => normalizedCode.includes(hint))) {
104
- return true;
105
- }
106
- const loweredMessage = message.toLowerCase();
107
- return RETRYABLE_SSE_MESSAGE_HINTS.some((hint) => loweredMessage.includes(hint));
108
- }
109
- function resolveMaxProviderAttempts() {
110
- const raw = String(process.env.ROUTECODEX_MAX_PROVIDER_ATTEMPTS || process.env.RCC_MAX_PROVIDER_ATTEMPTS || '')
111
- .trim()
112
- .toLowerCase();
113
- const parsed = raw ? Number.parseInt(raw, 10) : NaN;
114
- const candidate = Number.isFinite(parsed) ? parsed : DEFAULT_MAX_PROVIDER_ATTEMPTS;
115
- return Math.max(1, Math.min(20, candidate));
116
- }
117
- function resolveAntigravityMaxProviderAttempts() {
118
- const raw = String(process.env.ROUTECODEX_ANTIGRAVITY_MAX_PROVIDER_ATTEMPTS || process.env.RCC_ANTIGRAVITY_MAX_PROVIDER_ATTEMPTS || '')
119
- .trim()
120
- .toLowerCase();
121
- const parsed = raw ? Number.parseInt(raw, 10) : NaN;
122
- const candidate = Number.isFinite(parsed) ? parsed : DEFAULT_ANTIGRAVITY_MAX_PROVIDER_ATTEMPTS;
123
- return Math.max(1, Math.min(60, candidate));
124
- }
125
- function isAntigravityProviderKey(providerKey) {
126
- return typeof providerKey === 'string' && providerKey.startsWith('antigravity.');
127
- }
128
- function extractStatusCodeFromError(err) {
129
- if (!err || typeof err !== 'object')
130
- return undefined;
131
- const direct = err.statusCode;
132
- if (typeof direct === 'number')
133
- return direct;
134
- const nested = err.status;
135
- if (typeof nested === 'number')
136
- return nested;
137
- return undefined;
138
- }
139
- function isCredentialMissingInitError(error) {
140
- const message = error instanceof Error ? error.message : String(error ?? 'unknown');
141
- const code = error && typeof error === 'object' && typeof error.code === 'string'
142
- ? String(error.code).trim().toUpperCase()
143
- : '';
144
- if (code.includes('AUTH_MISSING') || code.includes('MISSING_API_KEY')) {
145
- return { missing: true, reason: message };
146
- }
147
- const normalized = message.toLowerCase();
148
- if (/environment variable\s+[a-z0-9_]+\s+is not defined/i.test(message)) {
149
- return { missing: true, reason: message };
150
- }
151
- if (normalized.includes('missing api key')) {
152
- return { missing: true, reason: message };
153
- }
154
- if (normalized.includes('missing inline apikey value')) {
155
- return { missing: true, reason: message };
156
- }
157
- if (normalized.includes('token is missing')) {
158
- return { missing: true, reason: message };
159
- }
160
- if (normalized.includes('expected tokenfile')) {
161
- return { missing: true, reason: message };
162
- }
163
- return { missing: false, reason: message };
164
- }
165
- /**
166
- * RouteCodex Server V2
167
- *
168
- * 与V1完全并行实现,集成系统hooks
169
- */
24
+ import { resolveHubShadowCompareConfig } from './hub-shadow-compare.js';
25
+ import { resolveLlmsEngineShadowConfig } from '../../../utils/llms-engine-shadow.js';
26
+ import { createRequestExecutor } from './request-executor.js';
27
+ import { startClockReaper, stopClockReaper } from './clock-client-reaper.js';
28
+ import { resolveVirtualRouterInput, getModuleDependencies, registerDaemonAdminUiRoute, getErrorHandlingShim, createDebugCenterShim, updateProviderProfiles, ensureProviderProfilesFromUserConfig, tryBuildProfiles, findProviderProfile, applyProviderProfileOverrides, canonicalizeRuntimeProvider, logStage, extractProviderModel, buildProviderLabel, normalizeAuthType, resolveSecretValue, isSafeSecretReference, bootstrapVirtualRouter, ensureHubPipelineCtor, ensureHubPipelineEngineShadow, isPipelineReady, waitForRuntimeReady, isQuotaRoutingEnabled, shouldStartManagerDaemon, initializeRouteErrorHub } from './http-server-bootstrap.js';
29
+ import { shouldEnableClockDaemonInjectLoop, resolveRawClockConfig, stopClockDaemonInjectLoop, startClockDaemonInjectLoop, tickClockDaemonInjectLoop } from './http-server-clock-daemon.js';
30
+ import { setupRuntime } from './http-server-runtime-setup.js';
31
+ import { initializeProviderRuntimes, createProviderHandle, materializeRuntimeProfile, normalizeRuntimeBaseUrl, resolveRuntimeAuth, resolveApiKeyValue, isLocalBaseUrl, disposeProviders } from './http-server-runtime-providers.js';
32
+ import { initializeHttpServer, restartRuntimeFromDisk, startHttpServer, stopHttpServer, getHttpServerStatus, getHttpServerConfig, isHttpServerInitialized, isHttpServerRunning, handleHttpServerError, initializeWithUserConfig, reloadHttpServerRuntime, buildHttpHandlerContext } from './http-server-lifecycle.js';
33
+ import { executePipelineViaLegacyOverride } from './http-server-legacy-pipeline.js';
170
34
  export class RouteCodexHttpServer {
171
35
  app;
172
36
  server;
@@ -175,7 +39,6 @@ export class RouteCodexHttpServer {
175
39
  errorHandling;
176
40
  _isInitialized = false;
177
41
  _isRunning = false;
178
- // Runtime state
179
42
  hubPipeline = null;
180
43
  providerHandles = new Map();
181
44
  providerKeyToRuntimeKey = new Map();
@@ -210,14 +73,15 @@ export class RouteCodexHttpServer {
210
73
  clockDaemonInjectTimer = null;
211
74
  clockDaemonInjectTickInFlight = false;
212
75
  lastClockDaemonInjectErrorAtMs = 0;
76
+ clockDaemonInjectSkipLogByKey = new Map();
213
77
  lastClockDaemonCleanupAtMs = 0;
78
+ requestExecutor;
214
79
  constructor(config) {
215
80
  this.config = config;
216
81
  this.app = express();
217
82
  this.errorHandling = new QuietErrorHandlingCenter();
218
83
  this.stageLoggingEnabled = isStageLoggingEnabled();
219
84
  this.repoRoot = resolveRepoRoot(import.meta.url);
220
- // Ensure session-scoped routing state does not leak across server instances.
221
85
  ensureServerScopedSessionDir(canonicalizeServerId(this.config.server.host, this.config.server.port));
222
86
  try {
223
87
  this.pipelineLogger = new PipelineDebugLoggerImpl({ colored: this.coloredLogger }, { enableConsoleLogging: true });
@@ -230,8 +94,28 @@ export class RouteCodexHttpServer {
230
94
  this.runtimeReadyResolve = resolve;
231
95
  this.runtimeReadyReject = reject;
232
96
  });
233
- // Register critical routes early (before provider initialization)
234
- // This ensures OAuth Portal is available when providers check token validity
97
+ this.requestExecutor = createRequestExecutor({
98
+ runtimeManager: {
99
+ resolveRuntimeKey: (providerKey, fallback) => {
100
+ if (providerKey && this.providerKeyToRuntimeKey.has(providerKey)) {
101
+ return this.providerKeyToRuntimeKey.get(providerKey);
102
+ }
103
+ return fallback;
104
+ },
105
+ getHandleByRuntimeKey: (runtimeKey) => {
106
+ if (!runtimeKey) {
107
+ return undefined;
108
+ }
109
+ return this.providerHandles.get(runtimeKey);
110
+ }
111
+ },
112
+ getHubPipeline: () => this.hubPipeline,
113
+ getModuleDependencies: () => this.getModuleDependencies(),
114
+ logStage: (stage, requestId, details) => {
115
+ this.logStage(stage, requestId, details);
116
+ },
117
+ stats: this.stats
118
+ });
235
119
  registerApiKeyAuthMiddleware(this.app, this.config);
236
120
  registerDefaultMiddleware(this.app);
237
121
  registerOAuthPortalRoute(this.app);
@@ -240,2570 +124,168 @@ export class RouteCodexHttpServer {
240
124
  console.log('[RouteCodexHttpServer] Initialized (pipeline=hub)');
241
125
  }
242
126
  resolveVirtualRouterInput(userConfig) {
243
- if (userConfig?.virtualrouter && typeof userConfig.virtualrouter === 'object') {
244
- return userConfig.virtualrouter;
245
- }
246
- return userConfig;
127
+ return resolveVirtualRouterInput(this, userConfig);
247
128
  }
248
129
  getModuleDependencies() {
249
- if (!this.moduleDependencies) {
250
- this.moduleDependencies = {
251
- errorHandlingCenter: this.getErrorHandlingShim(),
252
- debugCenter: this.createDebugCenterShim(),
253
- logger: this.pipelineLogger
254
- };
255
- }
256
- return this.moduleDependencies;
130
+ return getModuleDependencies(this);
257
131
  }
258
- /**
259
- * Register Daemon Admin UI route.
260
- * Serves docs/daemon-admin-ui.html as a static page.
261
- * Note: daemon-admin UI/API now uses password login (stored at ~/.routecodex/login) instead of httpserver.apikey.
262
- */
263
132
  registerDaemonAdminUiRoute() {
264
- this.app.get('/daemon/admin', async (req, res) => {
265
- try {
266
- const fs = await import('node:fs/promises');
267
- let html = '';
268
- try {
269
- const filePath = new URL('../../../../docs/daemon-admin-ui.html', import.meta.url);
270
- html = await fs.readFile(filePath, 'utf8');
271
- }
272
- catch {
273
- // build output reads from dist/docs; fallback to cwd/docs for dev runners
274
- const path = await import('node:path');
275
- const fallback = path.join(process.cwd(), 'docs', 'daemon-admin-ui.html');
276
- html = await fs.readFile(fallback, 'utf8');
277
- }
278
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
279
- // Avoid stale admin UI in browsers / proxies after upgrades.
280
- res.setHeader('Cache-Control', 'no-store, max-age=0');
281
- res.setHeader('Pragma', 'no-cache');
282
- res.setHeader('X-RouteCodex-Version', buildInfo?.version ? String(buildInfo.version) : String(process.env.ROUTECODEX_VERSION || 'dev'));
283
- res.send(html);
284
- }
285
- catch (error) {
286
- const message = error instanceof Error ? error.message : String(error);
287
- res.status(500).json({
288
- error: {
289
- message: `Daemon admin UI not available: ${message}`
290
- }
291
- });
292
- }
293
- });
133
+ registerDaemonAdminUiRoute(this);
294
134
  }
295
135
  getErrorHandlingShim() {
296
- if (!this.errorHandlingShim) {
297
- this.errorHandlingShim = {
298
- handleError: async (errorPayload, contextPayload) => {
299
- const sanitizedError = formatErrorForErrorCenter(errorPayload);
300
- const sanitizedContext = formatErrorForErrorCenter(contextPayload);
301
- await this.errorHandling.handleError({
302
- error: sanitizedError,
303
- source: 'pipeline',
304
- severity: 'medium',
305
- timestamp: Date.now(),
306
- context: sanitizedContext
307
- });
308
- },
309
- createContext: () => ({}),
310
- getStatistics: () => ({})
311
- };
312
- }
313
- return this.errorHandlingShim;
136
+ return getErrorHandlingShim(this);
314
137
  }
315
138
  createDebugCenterShim() {
316
- return {
317
- logDebug: () => { },
318
- logError: () => { },
319
- logModule: () => { },
320
- processDebugEvent: () => { },
321
- getLogs: () => []
322
- };
139
+ return createDebugCenterShim();
323
140
  }
324
141
  updateProviderProfiles(collection, rawConfig) {
325
- this.providerProfileIndex.clear();
326
- const source = collection ?? this.tryBuildProfiles(rawConfig);
327
- if (!source) {
328
- return;
329
- }
330
- for (const profile of source.profiles) {
331
- if (profile && typeof profile.id === 'string' && profile.id.trim()) {
332
- this.providerProfileIndex.set(profile.id.trim(), profile);
333
- }
334
- }
142
+ updateProviderProfiles(this, collection, rawConfig);
335
143
  }
336
144
  ensureProviderProfilesFromUserConfig() {
337
- if (this.providerProfileIndex.size > 0) {
338
- return;
339
- }
340
- const fallback = this.tryBuildProfiles(this.userConfig);
341
- if (!fallback) {
342
- return;
343
- }
344
- for (const profile of fallback.profiles) {
345
- if (profile && typeof profile.id === 'string' && profile.id.trim()) {
346
- this.providerProfileIndex.set(profile.id.trim(), profile);
347
- }
348
- }
145
+ ensureProviderProfilesFromUserConfig(this);
349
146
  }
350
147
  tryBuildProfiles(config) {
351
- if (!config) {
352
- return null;
353
- }
354
- try {
355
- return buildProviderProfiles(config);
356
- }
357
- catch {
358
- return null;
359
- }
148
+ return tryBuildProfiles(config);
360
149
  }
361
150
  findProviderProfile(runtime) {
362
- const candidates = new Set();
363
- const pushCandidate = (value) => {
364
- if (typeof value === 'string' && value.trim()) {
365
- candidates.add(value.trim());
366
- }
367
- };
368
- pushCandidate(runtime.providerId);
369
- if (runtime.providerKey && runtime.providerKey.includes('.')) {
370
- pushCandidate(runtime.providerKey.split('.')[0]);
371
- }
372
- if (runtime.runtimeKey && runtime.runtimeKey.includes('.')) {
373
- pushCandidate(runtime.runtimeKey.split('.')[0]);
374
- }
375
- for (const candidate of candidates) {
376
- const profile = this.providerProfileIndex.get(candidate);
377
- if (profile) {
378
- return profile;
379
- }
380
- }
381
- return undefined;
151
+ return findProviderProfile(this, runtime);
382
152
  }
383
153
  applyProviderProfileOverrides(runtime) {
384
- const profile = this.findProviderProfile(runtime);
385
- if (!profile) {
386
- return this.canonicalizeRuntimeProvider(runtime);
387
- }
388
- const patched = { ...runtime };
389
- const originalFamily = patched.providerFamily || patched.providerType;
390
- patched.providerFamily = originalFamily;
391
- patched.providerType = profile.protocol;
392
- if (profile.moduleType && profile.moduleType.trim()) {
393
- patched.providerModule = profile.moduleType.trim();
394
- }
395
- if (!patched.baseUrl && profile.transport.baseUrl) {
396
- patched.baseUrl = profile.transport.baseUrl;
397
- }
398
- if (!patched.endpoint && profile.transport.endpoint) {
399
- patched.endpoint = profile.transport.endpoint;
400
- }
401
- if (!patched.headers && profile.transport.headers) {
402
- patched.headers = profile.transport.headers;
403
- }
404
- if (patched.timeoutMs === undefined && typeof profile.transport.timeoutMs === 'number') {
405
- patched.timeoutMs = profile.transport.timeoutMs;
406
- }
407
- if (patched.maxRetries === undefined && typeof profile.transport.maxRetries === 'number') {
408
- patched.maxRetries = profile.transport.maxRetries;
409
- }
410
- if (!patched.compatibilityProfile && profile.compatibilityProfile) {
411
- patched.compatibilityProfile = profile.compatibilityProfile;
412
- }
413
- if (!patched.defaultModel && profile.metadata?.defaultModel) {
414
- patched.defaultModel = profile.metadata.defaultModel;
415
- }
416
- if (!patched.deepseek && profile.metadata?.deepseek) {
417
- patched.deepseek = profile.metadata.deepseek;
418
- }
419
- return this.canonicalizeRuntimeProvider(patched);
154
+ return applyProviderProfileOverrides(this, runtime);
420
155
  }
421
156
  canonicalizeRuntimeProvider(runtime) {
422
- const { providerType, providerFamily } = resolveProviderIdentity(runtime.providerType, runtime.providerFamily);
423
- return {
424
- ...runtime,
425
- providerType: providerType,
426
- providerFamily
427
- };
157
+ return canonicalizeRuntimeProvider(runtime);
428
158
  }
429
159
  logStage(stage, requestId, details) {
430
- if (!this.stageLoggingEnabled) {
431
- return;
432
- }
433
- logPipelineStage(stage, requestId, details);
160
+ logStage(this, stage, requestId, details);
434
161
  }
435
162
  extractProviderModel(payload) {
436
- if (!payload) {
437
- return undefined;
438
- }
439
- const source = payload.data && typeof payload.data === 'object'
440
- ? payload.data
441
- : payload;
442
- const raw = source.model;
443
- if (typeof raw === 'string' && raw.trim()) {
444
- return raw.trim();
445
- }
446
- return undefined;
163
+ return extractProviderModel(this, payload);
447
164
  }
448
165
  buildProviderLabel(providerKey, model) {
449
- const key = typeof providerKey === 'string' && providerKey.trim() ? providerKey.trim() : undefined;
450
- const modelId = typeof model === 'string' && model.trim() ? model.trim() : undefined;
451
- if (!key && !modelId) {
452
- return undefined;
453
- }
454
- if (key && modelId) {
455
- return `${key}.${modelId}`;
456
- }
457
- return key || modelId;
166
+ return buildProviderLabel(this, providerKey, model);
458
167
  }
459
168
  normalizeAuthType(input) {
460
- const value = typeof input === 'string' ? input.toLowerCase() : '';
461
- if (value.includes('oauth')) {
462
- return 'oauth';
463
- }
464
- return 'apikey';
169
+ return normalizeAuthType(this, input);
465
170
  }
466
171
  async resolveSecretValue(raw) {
467
- if (!raw) {
468
- throw new Error('Secret reference is required but missing');
469
- }
470
- const trimmed = raw.trim();
471
- const envMatch = trimmed.match(/^\$\{([A-Z0-9_]+)\}$/i);
472
- if (envMatch) {
473
- const envValue = process.env[envMatch[1]];
474
- if (!envValue) {
475
- throw new Error(`Environment variable ${envMatch[1]} is not defined`);
476
- }
477
- return envValue;
478
- }
479
- if (/^[A-Z][A-Z0-9_]+$/.test(trimmed)) {
480
- const envValue = process.env[trimmed];
481
- if (!envValue) {
482
- throw new Error(`Environment variable ${trimmed} is not defined`);
483
- }
484
- return envValue;
485
- }
486
- if (trimmed.startsWith('authfile-')) {
487
- return await this.authResolver.resolveKey(trimmed);
488
- }
489
- return trimmed;
172
+ return await resolveSecretValue(this, raw);
490
173
  }
491
174
  isSafeSecretReference(value) {
492
- const trimmed = value.trim();
493
- if (!trimmed) {
494
- return false;
495
- }
496
- if (trimmed.startsWith('authfile-')) {
497
- return true;
498
- }
499
- if (/^\$\{[A-Z0-9_]+\}$/i.test(trimmed)) {
500
- return true;
501
- }
502
- if (/^[A-Z][A-Z0-9_]+$/.test(trimmed)) {
503
- return true;
504
- }
505
- return false;
175
+ return isSafeSecretReference(this, value);
506
176
  }
507
177
  async bootstrapVirtualRouter(input) {
508
- const artifacts = (await bootstrapVirtualRouterConfig(input));
509
- return artifacts;
178
+ return await bootstrapVirtualRouter(this, input);
510
179
  }
511
180
  async ensureHubPipelineCtor() {
512
- if (this.hubPipelineCtor) {
513
- return this.hubPipelineCtor;
514
- }
515
- const ctorFactory = await getHubPipelineCtor();
516
- this.hubPipelineCtor = ctorFactory;
517
- return this.hubPipelineCtor;
181
+ return await ensureHubPipelineCtor(this);
518
182
  }
519
183
  async ensureHubPipelineEngineShadow() {
520
- if (this.hubPipelineEngineShadow) {
521
- return this.hubPipelineEngineShadow;
522
- }
523
- if (!this.hubPipelineConfigForShadow) {
524
- throw new Error('Hub pipeline shadow config is not initialized');
525
- }
526
- const baseConfig = this.hubPipelineConfigForShadow;
527
- const shadowConfig = { ...baseConfig };
528
- // Avoid double side effects when shadow-running the pipeline: keep reads, drop writes.
529
- const routingStateStore = baseConfig.routingStateStore;
530
- if (routingStateStore && typeof routingStateStore.loadSync === 'function') {
531
- shadowConfig.routingStateStore = {
532
- loadSync: routingStateStore.loadSync.bind(routingStateStore),
533
- saveAsync: () => { }
534
- };
535
- }
536
- const healthStore = baseConfig.healthStore;
537
- if (healthStore && typeof healthStore.loadInitialSnapshot === 'function') {
538
- shadowConfig.healthStore = {
539
- loadInitialSnapshot: healthStore.loadInitialSnapshot.bind(healthStore)
540
- };
541
- }
542
- const quotaViewReadOnly = baseConfig.quotaViewReadOnly;
543
- if (typeof quotaViewReadOnly === 'function') {
544
- shadowConfig.quotaView = quotaViewReadOnly;
545
- }
546
- const bridge = (await import('../../../modules/llmswitch/bridge.js'));
547
- const getCtor = bridge.getHubPipelineCtorForImpl;
548
- if (typeof getCtor !== 'function') {
549
- throw new Error('llmswitch bridge does not expose getHubPipelineCtorForImpl');
550
- }
551
- const ctorFactory = await getCtor('engine');
552
- const hubCtor = ctorFactory;
553
- if (!('virtualRouter' in shadowConfig)) {
554
- throw new Error('HubPipeline shadow config missing virtualRouter');
555
- }
556
- this.hubPipelineEngineShadow = new hubCtor(shadowConfig);
557
- return this.hubPipelineEngineShadow;
184
+ return await ensureHubPipelineEngineShadow(this);
558
185
  }
559
186
  isPipelineReady() {
560
- return Boolean(this.hubPipeline);
187
+ return isPipelineReady(this);
561
188
  }
562
189
  async waitForRuntimeReady() {
563
- if (this.runtimeReadyResolved) {
564
- return;
565
- }
566
- if (this.runtimeReadyError) {
567
- throw this.runtimeReadyError;
568
- }
569
- const raw = String(process.env.ROUTECODEX_STARTUP_HOLD_MS || process.env.RCC_STARTUP_HOLD_MS || '').trim();
570
- const parsed = raw ? Number.parseInt(raw, 10) : NaN;
571
- const timeoutMs = Number.isFinite(parsed) && parsed > 0 ? parsed : 120_000;
572
- const timeoutPromise = new Promise((_resolve, reject) => {
573
- const timer = setTimeout(() => reject(new Error(`startup timeout after ${timeoutMs}ms`)), timeoutMs);
574
- try {
575
- timer.unref?.();
576
- }
577
- catch {
578
- // ignore
579
- }
580
- });
581
- await Promise.race([this.runtimeReadyPromise, timeoutPromise]);
190
+ await waitForRuntimeReady(this);
582
191
  }
583
192
  isQuotaRoutingEnabled() {
584
- const flag = this.config.server.quotaRoutingEnabled;
585
- if (typeof flag === 'boolean') {
586
- return flag;
587
- }
588
- return true;
193
+ return isQuotaRoutingEnabled(this);
589
194
  }
590
195
  shouldStartManagerDaemon() {
591
- const mockFlag = String(process.env.ROUTECODEX_USE_MOCK || '').trim();
592
- if (mockFlag === '1' || mockFlag.toLowerCase() === 'true') {
593
- return false;
594
- }
595
- if (process.env.ROUTECODEX_MOCK_CONFIG_PATH || process.env.ROUTECODEX_MOCK_SAMPLES_DIR) {
596
- return false;
597
- }
598
- return true;
196
+ return shouldStartManagerDaemon(this);
599
197
  }
600
198
  shouldEnableClockDaemonInjectLoop() {
601
- const raw = String(process.env.ROUTECODEX_CLOCK_DAEMON_INJECT_ENABLE || process.env.RCC_CLOCK_DAEMON_INJECT_ENABLE || '').trim().toLowerCase();
602
- if (raw === '0' || raw === 'false' || raw === 'off' || raw === 'no') {
603
- return false;
604
- }
605
- if (raw === '1' || raw === 'true' || raw === 'on' || raw === 'yes') {
606
- return true;
607
- }
608
- if (process.env.JEST_WORKER_ID || process.env.NODE_ENV === 'test') {
609
- return false;
610
- }
611
- return true;
199
+ return shouldEnableClockDaemonInjectLoop();
612
200
  }
613
201
  resolveRawClockConfig() {
614
- const user = this.userConfig && typeof this.userConfig === 'object' ? this.userConfig : {};
615
- const vr = user.virtualrouter && typeof user.virtualrouter === 'object' ? user.virtualrouter : null;
616
- if (vr && Object.prototype.hasOwnProperty.call(vr, 'clock')) {
617
- return vr.clock;
618
- }
619
- if (Object.prototype.hasOwnProperty.call(user, 'clock')) {
620
- return user.clock;
621
- }
622
- const artCfg = this.currentRouterArtifacts &&
623
- this.currentRouterArtifacts.config &&
624
- typeof this.currentRouterArtifacts.config === 'object'
625
- ? this.currentRouterArtifacts.config
626
- : null;
627
- if (artCfg && Object.prototype.hasOwnProperty.call(artCfg, 'clock')) {
628
- return artCfg.clock;
629
- }
630
- return undefined;
202
+ return resolveRawClockConfig(this);
631
203
  }
632
204
  stopClockDaemonInjectLoop() {
633
- if (this.clockDaemonInjectTimer) {
634
- clearInterval(this.clockDaemonInjectTimer);
635
- this.clockDaemonInjectTimer = null;
636
- }
205
+ stopClockDaemonInjectLoop(this);
637
206
  }
638
207
  startClockDaemonInjectLoop() {
639
- this.stopClockDaemonInjectLoop();
640
- if (!this.shouldEnableClockDaemonInjectLoop()) {
641
- return;
642
- }
643
- const rawTick = String(process.env.ROUTECODEX_CLOCK_DAEMON_INJECT_TICK_MS || process.env.RCC_CLOCK_DAEMON_INJECT_TICK_MS || '').trim();
644
- const parsedTick = rawTick ? Number.parseInt(rawTick, 10) : NaN;
645
- const tickMs = Number.isFinite(parsedTick) && parsedTick >= 200 ? Math.floor(parsedTick) : 1500;
646
- this.clockDaemonInjectTimer = setInterval(() => {
647
- void this.tickClockDaemonInjectLoop();
648
- }, tickMs);
649
- this.clockDaemonInjectTimer.unref?.();
650
- void this.tickClockDaemonInjectLoop();
208
+ startClockDaemonInjectLoop(this);
651
209
  }
652
210
  async tickClockDaemonInjectLoop() {
653
- if (this.clockDaemonInjectTickInFlight) {
654
- return;
655
- }
656
- this.clockDaemonInjectTickInFlight = true;
657
- try {
658
- const rawClockConfig = this.resolveRawClockConfig();
659
- const resolvedClockConfig = await resolveClockConfigSnapshot(rawClockConfig);
660
- if (!resolvedClockConfig) {
661
- return;
662
- }
663
- const clockConfig = toExactMatchClockConfig(resolvedClockConfig);
664
- const sessionDir = String(process.env.ROUTECODEX_SESSION_DIR || '').trim();
665
- const clockDir = sessionDir ? path.join(sessionDir, 'clock') : '';
666
- const entries = clockDir
667
- ? await fs.readdir(clockDir, { withFileTypes: true }).catch(() => [])
668
- : [];
669
- const registry = getClockClientRegistry();
670
- const now = Date.now();
671
- const cleanupRequestId = `clock_cleanup_${now}_${Math.random().toString(16).slice(2, 8)}`;
672
- const staleCleanup = registry.cleanupStaleHeartbeats({
673
- nowMs: now,
674
- terminateManagedTmuxSession: (tmuxSessionId) => killManagedTmuxSession(tmuxSessionId),
675
- terminateManagedClientProcess: (processInfo) => terminateManagedClientProcess(processInfo)
676
- });
677
- const deadTmuxCleanup = registry.cleanupDeadTmuxSessions({
678
- isTmuxSessionAlive,
679
- terminateManagedTmuxSession: (tmuxSessionId) => killManagedTmuxSession(tmuxSessionId),
680
- terminateManagedClientProcess: (processInfo) => terminateManagedClientProcess(processInfo)
681
- });
682
- const removedConversationSessionIds = Array.from(new Set([
683
- ...staleCleanup.removedConversationSessionIds,
684
- ...deadTmuxCleanup.removedConversationSessionIds
685
- ]));
686
- if (removedConversationSessionIds.length > 0) {
687
- for (const removedConversationSessionId of removedConversationSessionIds) {
688
- await clearClockTasksSnapshot({
689
- sessionId: removedConversationSessionId,
690
- config: clockConfig
691
- });
692
- }
693
- }
694
- const hasCleanupActions = staleCleanup.removedDaemonIds.length > 0
695
- || deadTmuxCleanup.removedDaemonIds.length > 0;
696
- if (hasCleanupActions && Date.now() - this.lastClockDaemonCleanupAtMs > 2000) {
697
- this.lastClockDaemonCleanupAtMs = Date.now();
698
- console.log('[RouteCodexHttpServer] clock daemon cleanup audit:', {
699
- requestId: cleanupRequestId,
700
- staleHeartbeat: {
701
- reason: 'heartbeat_timeout',
702
- staleAfterMs: staleCleanup.staleAfterMs,
703
- removedDaemonIds: staleCleanup.removedDaemonIds,
704
- removedTmuxSessionIds: staleCleanup.removedTmuxSessionIds,
705
- removedConversationSessionIds: staleCleanup.removedConversationSessionIds,
706
- killedTmuxSessionIds: staleCleanup.killedTmuxSessionIds,
707
- failedKillTmuxSessionIds: staleCleanup.failedKillTmuxSessionIds,
708
- skippedKillTmuxSessionIds: staleCleanup.skippedKillTmuxSessionIds,
709
- killedManagedClientPids: staleCleanup.killedManagedClientPids,
710
- failedKillManagedClientPids: staleCleanup.failedKillManagedClientPids,
711
- skippedKillManagedClientPids: staleCleanup.skippedKillManagedClientPids
712
- },
713
- deadTmux: {
714
- reason: 'tmux_not_alive',
715
- removedDaemonIds: deadTmuxCleanup.removedDaemonIds,
716
- removedTmuxSessionIds: deadTmuxCleanup.removedTmuxSessionIds,
717
- removedConversationSessionIds: deadTmuxCleanup.removedConversationSessionIds,
718
- killedTmuxSessionIds: deadTmuxCleanup.killedTmuxSessionIds,
719
- failedKillTmuxSessionIds: deadTmuxCleanup.failedKillTmuxSessionIds,
720
- skippedKillTmuxSessionIds: deadTmuxCleanup.skippedKillTmuxSessionIds,
721
- killedManagedClientPids: deadTmuxCleanup.killedManagedClientPids,
722
- failedKillManagedClientPids: deadTmuxCleanup.failedKillManagedClientPids,
723
- skippedKillManagedClientPids: deadTmuxCleanup.skippedKillManagedClientPids
724
- }
725
- });
726
- }
727
- for (const entry of entries) {
728
- if (!entry || typeof entry.name !== 'string') {
729
- continue;
730
- }
731
- if (!entry.name.endsWith('.json')) {
732
- continue;
733
- }
734
- if (typeof entry.isFile === 'function' && !entry.isFile()) {
735
- continue;
736
- }
737
- const sessionId = entry.name.slice(0, -'.json'.length).trim();
738
- if (!sessionId) {
739
- continue;
740
- }
741
- const reservationId = 'clockd_inject_' + now + '_' + Math.random().toString(16).slice(2, 8);
742
- const reserved = await reserveClockDueTasks({
743
- reservationId,
744
- sessionId,
745
- config: clockConfig,
746
- requestId: reservationId
747
- });
748
- if (!reserved || !reserved.reservation || typeof reserved.injectText !== 'string' || !reserved.injectText.trim()) {
749
- continue;
750
- }
751
- const bind = registry.bindConversationSession({ conversationSessionId: sessionId });
752
- const text = [
753
- '[Clock Reminder]: scheduled tasks are due.',
754
- reserved.injectText.trim(),
755
- 'You may call tools to complete these tasks.',
756
- 'MANDATORY: if waiting is needed, use the clock tool to schedule wake-up (clock.schedule) now; do not only promise to wait.'
757
- ].join('\n');
758
- const injected = await registry.inject({
759
- sessionId,
760
- text,
761
- requestId: reservationId,
762
- source: 'clock.daemon.inject'
763
- });
764
- if (!injected.ok) {
765
- const nowWarn = Date.now();
766
- if (nowWarn - this.lastClockDaemonInjectErrorAtMs > 5000) {
767
- this.lastClockDaemonInjectErrorAtMs = nowWarn;
768
- console.warn('[RouteCodexHttpServer] clock daemon inject skipped:', {
769
- sessionId,
770
- injectReason: injected.reason,
771
- bindOk: bind.ok,
772
- bindReason: bind.reason
773
- });
774
- }
775
- continue;
776
- }
777
- await commitClockDueReservation({
778
- reservation: reserved.reservation,
779
- config: clockConfig
780
- });
781
- }
782
- }
783
- catch (error) {
784
- const now = Date.now();
785
- if (now - this.lastClockDaemonInjectErrorAtMs > 5000) {
786
- this.lastClockDaemonInjectErrorAtMs = now;
787
- console.warn('[RouteCodexHttpServer] clock daemon inject loop tick failed:', error);
788
- }
789
- }
790
- finally {
791
- this.clockDaemonInjectTickInFlight = false;
792
- }
211
+ await tickClockDaemonInjectLoop(this);
793
212
  }
794
- /**
795
- * 初始化服务器
796
- */
797
213
  async initialize() {
798
- try {
799
- console.log('[RouteCodexHttpServer] Starting initialization...');
800
- // 初始化错误处理
801
- await this.errorHandling.initialize();
802
- await this.initializeRouteErrorHub();
803
- // 初始化 ManagerDaemon 骨架(当前模块为占位实现,不改变行为)
804
- // Mock regressions run many short-lived servers; skip daemon to avoid flakiness from lingering background tasks.
805
- if (this.shouldStartManagerDaemon() && !this.managerDaemon) {
806
- const serverId = canonicalizeServerId(this.config.server.host, this.config.server.port);
807
- const daemon = new ManagerDaemon({ serverId, quotaRoutingEnabled: this.isQuotaRoutingEnabled() });
808
- daemon.registerModule(new TokenManagerModule());
809
- daemon.registerModule(new RoutingStateManagerModule());
810
- daemon.registerModule(new HealthManagerModule());
811
- // Quota manager(当前仅用于 antigravity/gemini-cli 等需要配额信息的 Provider)
812
- try {
813
- const mod = (await import('../../../manager/modules/quota/index.js'));
814
- if (typeof mod.QuotaManagerModule === 'function') {
815
- daemon.registerModule(new mod.QuotaManagerModule());
816
- }
817
- }
818
- catch {
819
- // 可选模块,缺失时忽略
820
- }
821
- await daemon.start();
822
- this.managerDaemon = daemon;
823
- }
824
- // registerDefaultMiddleware and registerOAuthPortalRoute already called in constructor
825
- // Register remaining HTTP routes
826
- registerHttpRoutes({
827
- app: this.app,
828
- config: this.config,
829
- buildHandlerContext: () => this.buildHandlerContext(),
830
- getPipelineReady: () => this.isPipelineReady(),
831
- waitForPipelineReady: async () => await this.waitForRuntimeReady(),
832
- handleError: (error, context) => this.handleError(error, context),
833
- restartRuntimeFromDisk: async () => await this.restartRuntimeFromDisk(),
834
- getHealthSnapshot: () => {
835
- const healthModule = this.managerDaemon?.getModule('health');
836
- return healthModule?.getCurrentSnapshot() ?? null;
837
- },
838
- getRoutingState: (sessionId) => {
839
- const routingModule = this.managerDaemon?.getModule('routing');
840
- const store = routingModule?.getRoutingStateStore();
841
- if (!store) {
842
- return null;
843
- }
844
- const key = sessionId && sessionId.trim() ? `session:${sessionId.trim()}` : '';
845
- return key ? store.loadSync(key) : null;
846
- },
847
- getManagerDaemon: () => this.managerDaemon,
848
- getVirtualRouterArtifacts: () => this.currentRouterArtifacts,
849
- getStatsSnapshot: () => ({
850
- session: this.stats.snapshot(Math.round(process.uptime() * 1000)),
851
- historical: this.stats.snapshotHistorical()
852
- }),
853
- getServerId: () => canonicalizeServerId(this.config.server.host, this.config.server.port)
854
- });
855
- this._isInitialized = true;
856
- console.log('[RouteCodexHttpServer] Initialization completed successfully');
857
- }
858
- catch (error) {
859
- await this.handleError(error, 'initialization');
860
- throw error;
861
- }
214
+ await initializeHttpServer(this);
862
215
  }
863
216
  async restartRuntimeFromDisk() {
864
- // Serialize restarts to avoid racing provider disposals / hub updates.
865
- const run = async () => {
866
- const loaded = await loadRouteCodexConfig(this.config?.configPath);
867
- const userConfig = asRecord(loaded.userConfig) ?? {};
868
- const httpServerNode = asRecord(userConfig.httpserver) ??
869
- asRecord(asRecord(userConfig.modules)?.httpserver)?.config ??
870
- null;
871
- const nextApiKey = httpServerNode ? (typeof httpServerNode.apikey === 'string' ? String(httpServerNode.apikey).trim() : '') : '';
872
- const nextHost = httpServerNode ? (typeof httpServerNode.host === 'string' ? String(httpServerNode.host).trim() : '') : '';
873
- const nextPort = httpServerNode ? (typeof httpServerNode.port === 'number' ? Number(httpServerNode.port) : NaN) : NaN;
874
- const warnings = [];
875
- // Best-effort: allow rotating apikey at runtime by mutating config object.
876
- if (typeof nextApiKey === 'string' && nextApiKey !== String(this.config.server.apikey || '')) {
877
- this.config.server.apikey = nextApiKey || undefined;
878
- }
879
- // host/port changes require rebind; record warnings but do not attempt to re-listen.
880
- if (nextHost && nextHost !== this.config.server.host) {
881
- warnings.push(`httpserver.host changed to "${nextHost}" but live server keeps "${this.config.server.host}" until process restart`);
882
- }
883
- if (Number.isFinite(nextPort) && nextPort > 0 && nextPort !== this.config.server.port) {
884
- warnings.push(`httpserver.port changed to ${nextPort} but live server keeps ${this.config.server.port} until process restart`);
885
- }
886
- // Keep the server's configPath aligned with what was loaded.
887
- this.config.configPath = loaded.configPath;
888
- await this.reloadRuntime(loaded.userConfig, { providerProfiles: loaded.providerProfiles });
889
- return { reloadedAt: Date.now(), configPath: loaded.configPath, ...(warnings.length ? { warnings } : {}) };
890
- };
891
- const slot = this.restartChain.then(run);
892
- this.restartChain = slot.then(() => undefined, () => undefined);
893
- return await slot;
217
+ return await restartRuntimeFromDisk(this);
894
218
  }
895
- /**
896
- * 启动服务器
897
- */
898
219
  async start() {
899
- if (!this._isInitialized) {
900
- await this.initialize();
901
- }
902
- return new Promise((resolve, reject) => {
903
- this.server = this.app.listen(this.config.server.port, this.config.server.host, () => {
904
- this._isRunning = true;
905
- const boundAddress = this.server?.address();
906
- const resolvedPort = boundAddress && typeof boundAddress === 'object' && typeof boundAddress.port === 'number'
907
- ? boundAddress.port
908
- : this.config.server.port;
909
- process.env.ROUTECODEX_SERVER_PORT = String(resolvedPort);
910
- console.log(`[RouteCodexHttpServer] Server started on ${this.config.server.host}:${resolvedPort}`);
911
- resolve();
912
- });
913
- // In test runners (Jest), prevent the listen handle from keeping the process alive
914
- // in case some keep-alive sockets linger.
915
- if (process.env.JEST_WORKER_ID || process.env.NODE_ENV === 'test') {
916
- try {
917
- this.server.unref?.();
918
- }
919
- catch {
920
- // ignore
921
- }
922
- }
923
- this.server.on('connection', (socket) => {
924
- this.activeSockets.add(socket);
925
- socket.on('close', () => {
926
- this.activeSockets.delete(socket);
927
- });
928
- });
929
- this.server.on('error', async (error) => {
930
- await this.handleError(error, 'server_start');
931
- reject(error);
932
- });
933
- });
220
+ await startHttpServer(this);
221
+ // Start the clock reaper after server is running
222
+ startClockReaper();
934
223
  }
935
- /**
936
- * 停止服务器
937
- */
938
224
  async stop() {
939
- this.stopClockDaemonInjectLoop();
940
- try {
941
- await shutdownCamoufoxLaunchers();
942
- }
943
- catch {
944
- // ignore launcher cleanup errors
945
- }
946
- if (!this.server) {
947
- return;
948
- }
949
- if (this.server) {
950
- // Best-effort: close any open keep-alive sockets so server.close can finish.
951
- for (const socket of this.activeSockets) {
952
- try {
953
- socket.destroy();
954
- }
955
- catch {
956
- // ignore
957
- }
958
- }
959
- this.activeSockets.clear();
960
- try {
961
- const srv = this.server;
962
- srv.closeIdleConnections?.();
963
- srv.closeAllConnections?.();
964
- }
965
- catch {
966
- // ignore
967
- }
968
- return new Promise(resolve => {
969
- this.server?.close(async () => {
970
- this._isRunning = false;
971
- try {
972
- await this.disposeProviders();
973
- }
974
- catch { /* ignore */ }
975
- try {
976
- if (this.managerDaemon) {
977
- await this.managerDaemon.stop();
978
- this.managerDaemon = null;
979
- }
980
- }
981
- catch {
982
- // ignore manager shutdown failures
983
- }
984
- try {
985
- this.server?.removeAllListeners();
986
- }
987
- catch {
988
- // ignore
989
- }
990
- this.server = undefined;
991
- await this.errorHandling.destroy();
992
- try {
993
- const uptimeMs = Math.round(process.uptime() * 1000);
994
- const snapshot = this.stats.logSummary(uptimeMs);
995
- await this.stats.persistSnapshot(snapshot, { reason: 'server_shutdown' });
996
- await this.stats.logHistoricalSummary();
997
- }
998
- catch {
999
- // stats logging must never block shutdown
1000
- }
1001
- console.log('[RouteCodexHttpServer] Server stopped');
1002
- resolve();
1003
- });
1004
- });
1005
- }
225
+ stopClockReaper();
226
+ await stopHttpServer(this);
1006
227
  }
1007
- /**
1008
- * 获取服务器状态
1009
- */
1010
228
  getStatus() {
1011
- return {
1012
- initialized: this._isInitialized,
1013
- running: this._isRunning,
1014
- port: this.config.server.port,
1015
- host: this.config.server.host,
1016
- uptime: process.uptime(),
1017
- memory: process.memoryUsage(),
1018
- version: 'v2'
1019
- };
229
+ return getHttpServerStatus(this);
1020
230
  }
1021
- // Non-standard helper used by index.ts for logging URL
1022
231
  getServerConfig() {
1023
- return { host: this.config.server.host, port: this.config.server.port };
232
+ return getHttpServerConfig(this);
1024
233
  }
1025
- /**
1026
- * V1兼容接口:检查是否已初始化
1027
- */
1028
234
  isInitialized() {
1029
- return this._isInitialized;
235
+ return isHttpServerInitialized(this);
1030
236
  }
1031
- /**
1032
- * V1兼容接口:检查是否正在运行
1033
- */
1034
237
  isRunning() {
1035
- return this._isRunning;
238
+ return isHttpServerRunning(this);
1036
239
  }
1037
- /**
1038
- * 处理错误
1039
- */
1040
240
  async handleError(error, context) {
1041
- const payload = {
1042
- code: `SERVER_${context.toUpperCase()}`,
1043
- message: error.message || 'RouteCodex server error',
1044
- source: `routecodex-server-v2.${context}`,
1045
- scope: 'server',
1046
- severity: 'medium',
1047
- details: {
1048
- name: error.name,
1049
- stack: error.stack,
1050
- version: 'v2'
1051
- },
1052
- originalError: error
1053
- };
1054
- try {
1055
- await reportRouteError(payload);
1056
- }
1057
- catch (handlerError) {
1058
- console.error('[RouteCodexHttpServer] Failed to report error via RouteErrorHub:', formatValueForConsole(handlerError));
1059
- console.error('[RouteCodexHttpServer] Original error:', formatValueForConsole(error));
1060
- }
241
+ await handleHttpServerError(this, error, context);
1061
242
  }
1062
- // --- V1 parity helpers and attach methods ---
1063
243
  async initializeWithUserConfig(userConfig, context) {
1064
- try {
1065
- this.updateProviderProfiles(context?.providerProfiles, userConfig);
1066
- await this.setupRuntime(userConfig);
1067
- if (!this.runtimeReadyResolved) {
1068
- this.runtimeReadyResolved = true;
1069
- this.runtimeReadyResolve?.();
1070
- }
1071
- }
1072
- catch (error) {
1073
- const normalized = error instanceof Error ? error : new Error(String(error));
1074
- this.runtimeReadyError = normalized;
1075
- if (!this.runtimeReadyResolved) {
1076
- this.runtimeReadyReject?.(normalized);
1077
- }
1078
- throw error;
1079
- }
244
+ await initializeWithUserConfig(this, userConfig, context);
1080
245
  }
1081
246
  async reloadRuntime(userConfig, context) {
1082
- this.updateProviderProfiles(context?.providerProfiles, userConfig);
1083
- await this.setupRuntime(userConfig);
1084
- if (!this.runtimeReadyResolved) {
1085
- this.runtimeReadyResolved = true;
1086
- this.runtimeReadyResolve?.();
1087
- }
247
+ await reloadHttpServerRuntime(this, userConfig, context);
1088
248
  }
1089
249
  async setupRuntime(userConfig) {
1090
- this.userConfig = asRecord(userConfig);
1091
- this.ensureProviderProfilesFromUserConfig();
1092
- const routerInput = this.resolveVirtualRouterInput(this.userConfig);
1093
- const bootstrapArtifacts = await this.bootstrapVirtualRouter(routerInput);
1094
- this.currentRouterArtifacts = bootstrapArtifacts;
1095
- const hubCtor = await this.ensureHubPipelineCtor();
1096
- const hubConfig = {
1097
- virtualRouter: bootstrapArtifacts.config
1098
- };
1099
- // Unified Hub Framework V1: policy rollout toggle.
1100
- // Default: enforce (Phase 1: Responses-first outbound policy; currently only
1101
- // affects openai-responses provider outbound payload).
1102
- // - Disable via env: ROUTECODEX_HUB_POLICY_MODE=off
1103
- // - Enforce via env: ROUTECODEX_HUB_POLICY_MODE=enforce
1104
- const hubPolicyModeRaw = String(process.env.ROUTECODEX_HUB_POLICY_MODE || '').trim().toLowerCase();
1105
- const hubPolicyMode = hubPolicyModeRaw === 'off' || hubPolicyModeRaw === '0' || hubPolicyModeRaw === 'false'
1106
- ? null
1107
- : (hubPolicyModeRaw === 'observe' || hubPolicyModeRaw === 'enforce' ? hubPolicyModeRaw : 'enforce');
1108
- this.hubPolicyMode = hubPolicyMode ?? 'off';
1109
- if (hubPolicyMode) {
1110
- const sampleRateRaw = String(process.env.ROUTECODEX_HUB_POLICY_SAMPLE_RATE || '').trim();
1111
- const sampleRate = sampleRateRaw ? Number(sampleRateRaw) : undefined;
1112
- hubConfig.policy = {
1113
- mode: hubPolicyMode,
1114
- ...(Number.isFinite(sampleRate) ? { sampleRate } : {})
1115
- };
1116
- }
1117
- // Unified Hub Framework V1: tool surface rollout toggle (enforce by default in dev).
1118
- // - Disable via env: ROUTECODEX_HUB_TOOL_SURFACE_MODE=off
1119
- // - Observe via env: ROUTECODEX_HUB_TOOL_SURFACE_MODE=observe
1120
- // - Shadow (diff-only, no rewrites) via env: ROUTECODEX_HUB_TOOL_SURFACE_MODE=shadow
1121
- // - Enforce (rewrite outbound payload) via env: ROUTECODEX_HUB_TOOL_SURFACE_MODE=enforce
1122
- const toolSurfaceModeRaw = String(process.env.ROUTECODEX_HUB_TOOL_SURFACE_MODE || '').trim().toLowerCase();
1123
- const toolSurfaceMode = toolSurfaceModeRaw === 'off' || toolSurfaceModeRaw === '0' || toolSurfaceModeRaw === 'false'
1124
- ? null
1125
- : toolSurfaceModeRaw === 'observe' || toolSurfaceModeRaw === 'shadow' || toolSurfaceModeRaw === 'enforce'
1126
- ? toolSurfaceModeRaw
1127
- : buildInfo.mode === 'dev'
1128
- ? 'enforce'
1129
- : null;
1130
- if (toolSurfaceMode) {
1131
- const sampleRateRaw = String(process.env.ROUTECODEX_HUB_TOOL_SURFACE_SAMPLE_RATE || '').trim();
1132
- const sampleRate = sampleRateRaw ? Number(sampleRateRaw) : undefined;
1133
- hubConfig.toolSurface = {
1134
- mode: toolSurfaceMode,
1135
- ...(Number.isFinite(sampleRate) ? { sampleRate } : {})
1136
- };
1137
- // Also export the resolved mode to env so llmswitch-core response conversion
1138
- // (convertProviderResponse) can observe tool surface mismatches consistently.
1139
- if (!process.env.ROUTECODEX_HUB_TOOL_SURFACE_MODE) {
1140
- process.env.ROUTECODEX_HUB_TOOL_SURFACE_MODE = toolSurfaceMode;
1141
- }
1142
- }
1143
- // Unified Hub Framework V1: followup (servertool) shadow compare toggle.
1144
- // Implemented in llmswitch-core servertool engine; controlled by env for progressive rollout.
1145
- // Default: shadow in dev builds; off in release builds.
1146
- if (!process.env.ROUTECODEX_HUB_FOLLOWUP_MODE) {
1147
- if (buildInfo.mode === 'dev') {
1148
- process.env.ROUTECODEX_HUB_FOLLOWUP_MODE = 'shadow';
1149
- }
1150
- }
1151
- const healthModule = this.managerDaemon?.getModule('health');
1152
- const healthStore = healthModule?.getHealthStore();
1153
- if (healthStore) {
1154
- hubConfig.healthStore = healthStore;
1155
- }
1156
- const routingModule = this.managerDaemon?.getModule('routing');
1157
- const routingStateStore = routingModule?.getRoutingStateStore();
1158
- if (routingStateStore) {
1159
- hubConfig.routingStateStore = routingStateStore;
1160
- }
1161
- const quotaModule = this.managerDaemon?.getModule('quota');
1162
- if (this.isQuotaRoutingEnabled() && quotaModule && typeof quotaModule.getQuotaView === 'function') {
1163
- hubConfig.quotaView = quotaModule.getQuotaView();
1164
- if (typeof quotaModule.getQuotaViewReadOnly === 'function') {
1165
- hubConfig.quotaViewReadOnly = quotaModule.getQuotaViewReadOnly();
1166
- }
1167
- }
1168
- if (!this.hubPipeline) {
1169
- this.hubPipeline = new hubCtor(hubConfig);
1170
- }
1171
- else {
1172
- const existing = this.hubPipeline;
1173
- try {
1174
- existing.updateRuntimeDeps?.({
1175
- ...(healthStore ? { healthStore } : {}),
1176
- ...(routingStateStore ? { routingStateStore } : {}),
1177
- ...('quotaView' in hubConfig ? { quotaView: hubConfig.quotaView } : {})
1178
- });
1179
- }
1180
- catch {
1181
- // best-effort: runtime deps updates must never block reload
1182
- }
1183
- this.hubPipeline.updateVirtualRouterConfig(bootstrapArtifacts.config);
1184
- }
1185
- // llms-engine shadow: capture the latest hub config and reset shadow pipeline to avoid stale deps.
1186
- this.hubPipelineConfigForShadow = hubConfig;
1187
- this.hubPipelineEngineShadow = null;
1188
- await this.initializeProviderRuntimes(bootstrapArtifacts);
1189
- this.startClockDaemonInjectLoop();
250
+ await setupRuntime(this, userConfig);
1190
251
  }
1191
252
  buildHandlerContext() {
1192
- return {
1193
- executePipeline: this.executePipeline.bind(this),
1194
- errorHandling: this.errorHandling
1195
- };
253
+ return buildHttpHandlerContext(this);
1196
254
  }
1197
255
  async initializeProviderRuntimes(artifacts) {
1198
- const runtimeMap = artifacts?.targetRuntime ?? this.currentRouterArtifacts?.targetRuntime ?? undefined;
1199
- if (!runtimeMap) {
1200
- return;
1201
- }
1202
- await this.disposeProviders();
1203
- this.providerKeyToRuntimeKey.clear();
1204
- this.providerRuntimeInitErrors.clear();
1205
- this.runtimeKeyCredentialSkipped.clear();
1206
- this.startupExcludedProviderKeys.clear();
1207
- // Antigravity UA preload (best-effort):
1208
- // - fetch latest UA version once (cached)
1209
- // - load per-alias camoufox fingerprints so UA suffix stays stable per OAuth session
1210
- try {
1211
- const aliases = [];
1212
- for (const [providerKey, runtime] of Object.entries(runtimeMap)) {
1213
- const key = typeof providerKey === 'string' ? providerKey.trim() : '';
1214
- const rk = runtime && typeof runtime.runtimeKey === 'string'
1215
- ? String(runtime.runtimeKey).trim()
1216
- : '';
1217
- for (const candidate of [rk, key]) {
1218
- if (!candidate.toLowerCase().startsWith('antigravity.')) {
1219
- continue;
1220
- }
1221
- const parts = candidate.split('.');
1222
- if (parts.length >= 2 && parts[1] && parts[1].trim()) {
1223
- aliases.push(parts[1].trim());
1224
- }
1225
- }
1226
- }
1227
- if (aliases.length) {
1228
- await Promise.allSettled([
1229
- primeAntigravityUserAgentVersion(),
1230
- preloadAntigravityAliasUserAgents(aliases)
1231
- ]);
1232
- }
1233
- }
1234
- catch {
1235
- // UA preload must never block runtime init/reload
1236
- }
1237
- const quotaModule = this.managerDaemon?.getModule('quota');
1238
- // Antigravity warmup (strict): if UA fingerprint suffix doesn't match local OAuth fingerprint, blacklist the alias.
1239
- if (isAntigravityWarmupEnabled()) {
1240
- try {
1241
- const providerKeysByAlias = new Map();
1242
- for (const providerKey of Object.keys(runtimeMap)) {
1243
- const key = typeof providerKey === 'string' ? providerKey.trim() : '';
1244
- if (!key.toLowerCase().startsWith('antigravity.')) {
1245
- continue;
1246
- }
1247
- const parts = key.split('.');
1248
- if (parts.length < 3) {
1249
- continue;
1250
- }
1251
- const alias = parts[1]?.trim();
1252
- if (!alias) {
1253
- continue;
1254
- }
1255
- const list = providerKeysByAlias.get(alias) || [];
1256
- list.push(key);
1257
- providerKeysByAlias.set(alias, list);
1258
- }
1259
- if (providerKeysByAlias.size > 0) {
1260
- const canBlacklist = Boolean(quotaModule && typeof quotaModule.disableProvider === 'function');
1261
- const durationMs = getAntigravityWarmupBlacklistDurationMs();
1262
- let okCount = 0;
1263
- let failCount = 0;
1264
- for (const [alias, providerKeys] of providerKeysByAlias.entries()) {
1265
- const result = await warmupCheckAntigravityAlias(alias);
1266
- if (result.ok) {
1267
- okCount += 1;
1268
- console.log(`[antigravity:warmup] ok alias=${alias} profile=${result.profileId} fp_os=${result.fingerprintOs} fp_arch=${result.fingerprintArch} ua_suffix=${result.actualSuffix} ua=${result.actualUserAgent}`);
1269
- continue;
1270
- }
1271
- failCount += 1;
1272
- const expected = result.expectedSuffix ? ` expected=${result.expectedSuffix}` : '';
1273
- const actual = result.actualSuffix ? ` actual=${result.actualSuffix}` : '';
1274
- const hint = result.reason === 'linux_not_allowed'
1275
- ? ` hint="run: routecodex camoufox-fp repair --provider antigravity --alias ${alias}"`
1276
- : result.reason === 'reauth_required'
1277
- ? ` hint="run: routecodex oauth antigravity-auto ${result.tokenFile || `antigravity-oauth-*-` + alias + `.json`}"` +
1278
- `${result.fromSuffix ? ` from=${result.fromSuffix}` : ''}${result.toSuffix ? ` to=${result.toSuffix}` : ''}`
1279
- : '';
1280
- console.error(`[antigravity:warmup] FAIL alias=${alias} profile=${result.profileId}${result.fingerprintOs ? ` fp_os=${result.fingerprintOs}` : ''}${result.fingerprintArch ? ` fp_arch=${result.fingerprintArch}` : ''} reason=${result.reason}${expected}${actual}${hint} providerKeys=${providerKeys.length}${canBlacklist ? '' : ' (quota module unavailable; cannot blacklist)'}`);
1281
- if (canBlacklist) {
1282
- await Promise.allSettled(providerKeys.map((providerKey) => quotaModule.disableProvider({ providerKey, mode: 'blacklist', durationMs })));
1283
- }
1284
- }
1285
- console.log(`[antigravity:warmup] summary ok=${okCount} fail=${failCount} total=${providerKeysByAlias.size}`);
1286
- }
1287
- }
1288
- catch {
1289
- // warmup is best-effort; never block server startup
1290
- }
1291
- }
1292
- // Multiple providerKeys may share the same runtimeKey (single provider handle with multiple models).
1293
- // Quota is tracked by providerKey, so we need a stable way to derive authType for every key,
1294
- // even when the handle has already been created for this runtimeKey.
1295
- const runtimeKeyAuthType = new Map();
1296
- const apikeyDailyResetTime = (() => {
1297
- const vr = this.userConfig && typeof this.userConfig === 'object' ? this.userConfig.virtualrouter : null;
1298
- const quota = vr && typeof vr === 'object' && !Array.isArray(vr) ? vr.quota : null;
1299
- if (!quota || typeof quota !== 'object' || Array.isArray(quota)) {
1300
- return null;
1301
- }
1302
- const raw = typeof quota.apikeyDailyResetTime === 'string'
1303
- ? quota.apikeyDailyResetTime
1304
- : typeof quota.apikeyResetTime === 'string'
1305
- ? quota.apikeyResetTime
1306
- : typeof quota.apikeyReset === 'string'
1307
- ? quota.apikeyReset
1308
- : null;
1309
- const trimmed = typeof raw === 'string' ? raw.trim() : '';
1310
- return trimmed ? trimmed : null;
1311
- })();
1312
- const failedRuntimeKeys = new Set();
1313
- for (const [providerKey, runtime] of Object.entries(runtimeMap)) {
1314
- if (!runtime) {
1315
- continue;
1316
- }
1317
- const runtimeKey = runtime.runtimeKey || providerKey;
1318
- const authTypeFromRuntime = runtime && runtime.auth && typeof runtime.auth.type === 'string'
1319
- ? String(runtime.auth.type).trim()
1320
- : null;
1321
- if (failedRuntimeKeys.has(runtimeKey)) {
1322
- if (this.runtimeKeyCredentialSkipped.has(runtimeKey)) {
1323
- this.startupExcludedProviderKeys.add(providerKey);
1324
- }
1325
- continue;
1326
- }
1327
- if (!this.providerHandles.has(runtimeKey)) {
1328
- let patchedRuntime = null;
1329
- try {
1330
- const resolvedRuntime = await this.materializeRuntimeProfile(runtime);
1331
- patchedRuntime = this.applyProviderProfileOverrides(resolvedRuntime);
1332
- try {
1333
- const authTypeFromPatched = patchedRuntime && patchedRuntime.auth && typeof patchedRuntime.auth.type === 'string'
1334
- ? String(patchedRuntime.auth.type).trim()
1335
- : null;
1336
- if (authTypeFromPatched) {
1337
- runtimeKeyAuthType.set(runtimeKey, authTypeFromPatched);
1338
- }
1339
- else if (authTypeFromRuntime) {
1340
- runtimeKeyAuthType.set(runtimeKey, authTypeFromRuntime);
1341
- }
1342
- else if (!runtimeKeyAuthType.has(runtimeKey)) {
1343
- runtimeKeyAuthType.set(runtimeKey, null);
1344
- }
1345
- }
1346
- catch {
1347
- // ignore authType derivation failures
1348
- }
1349
- const handle = await this.createProviderHandle(runtimeKey, patchedRuntime);
1350
- this.providerHandles.set(runtimeKey, handle);
1351
- this.providerRuntimeInitErrors.delete(runtimeKey);
1352
- }
1353
- catch (error) {
1354
- // Non-blocking: do not crash server startup when a provider is misconfigured (e.g. missing env var).
1355
- // Emit a provider error so health/quota modules can mark it unhealthy and routing can fail over.
1356
- const credentialMissing = isCredentialMissingInitError(error);
1357
- failedRuntimeKeys.add(runtimeKey);
1358
- if (error instanceof Error) {
1359
- this.providerRuntimeInitErrors.set(runtimeKey, error);
1360
- }
1361
- else {
1362
- this.providerRuntimeInitErrors.set(runtimeKey, new Error(String(error)));
1363
- }
1364
- if (credentialMissing.missing) {
1365
- this.runtimeKeyCredentialSkipped.add(runtimeKey);
1366
- this.startupExcludedProviderKeys.add(providerKey);
1367
- console.warn(`[provider.init.skip] providerKey=${providerKey} runtimeKey=${runtimeKey} reason=credential_missing detail=${credentialMissing.reason}`);
1368
- continue;
1369
- }
1370
- try {
1371
- const { emitProviderError } = await import('../../../providers/core/utils/provider-error-reporter.js');
1372
- emitProviderError({
1373
- error,
1374
- stage: 'provider.runtime.init',
1375
- runtime: {
1376
- requestId: `startup_${Date.now()}`,
1377
- providerKey,
1378
- providerId: runtime.providerId || runtimeKey.split('.')[0],
1379
- providerType: String(runtime.providerType || runtime.providerType || 'unknown'),
1380
- providerProtocol: String(runtime.outboundProfile || ''),
1381
- routeName: 'startup',
1382
- pipelineId: 'startup',
1383
- runtimeKey
1384
- },
1385
- dependencies: this.getModuleDependencies(),
1386
- recoverable: false,
1387
- affectsHealth: true,
1388
- details: {
1389
- reason: 'provider_init_failed',
1390
- runtimeKey,
1391
- providerKey
1392
- }
1393
- });
1394
- }
1395
- catch {
1396
- // ignore emit failures
1397
- }
1398
- // Skip this providerKey mapping; requests will fail over if routing selects it.
1399
- continue;
1400
- }
1401
- }
1402
- // Register static quota metadata for every providerKey (not just per runtimeKey).
1403
- // Use the runtimeKey authType when available so shared runtimeKey models inherit correct policy.
1404
- try {
1405
- const authType = runtimeKeyAuthType.get(runtimeKey) ?? authTypeFromRuntime;
1406
- const apikeyResetForKey = authType === 'apikey' ? apikeyDailyResetTime : null;
1407
- quotaModule?.registerProviderStaticConfig?.(providerKey, {
1408
- authType: authType ?? null,
1409
- apikeyDailyResetTime: apikeyResetForKey
1410
- });
1411
- }
1412
- catch {
1413
- // best-effort: quota static config registration must never block runtime init
1414
- }
1415
- // Only map providerKey when runtimeKey has an initialized handle.
1416
- if (this.providerHandles.has(runtimeKey)) {
1417
- this.providerKeyToRuntimeKey.set(providerKey, runtimeKey);
1418
- }
1419
- }
256
+ await initializeProviderRuntimes(this, artifacts);
1420
257
  }
1421
258
  async createProviderHandle(runtimeKey, runtime) {
1422
- const protocolType = normalizeProviderType(runtime.providerType);
1423
- const providerFamily = runtime.providerFamily || protocolType;
1424
- const providerProtocol = (typeof runtime.outboundProfile === 'string' && runtime.outboundProfile.trim()
1425
- ? runtime.outboundProfile.trim()
1426
- : undefined) ?? mapProviderProtocol(protocolType);
1427
- const instance = ProviderFactory.createProviderFromRuntime(runtime, this.getModuleDependencies());
1428
- await instance.initialize();
1429
- const providerId = runtime.providerId || runtimeKey.split('.')[0];
1430
- return {
1431
- runtimeKey,
1432
- providerId,
1433
- providerType: protocolType,
1434
- providerFamily,
1435
- providerProtocol,
1436
- runtime,
1437
- instance
1438
- };
259
+ return await createProviderHandle(this, runtimeKey, runtime);
1439
260
  }
1440
261
  async materializeRuntimeProfile(runtime) {
1441
- const auth = await this.resolveRuntimeAuth(runtime);
1442
- const baseUrl = this.normalizeRuntimeBaseUrl(runtime);
1443
- const identity = resolveProviderIdentity(runtime.providerType, runtime.providerFamily);
1444
- return {
1445
- ...runtime,
1446
- ...(baseUrl ? { baseUrl } : {}),
1447
- providerType: identity.providerType,
1448
- providerFamily: identity.providerFamily,
1449
- auth
1450
- };
262
+ return await materializeRuntimeProfile(this, runtime);
1451
263
  }
1452
264
  normalizeRuntimeBaseUrl(runtime) {
1453
- const candidates = [runtime.baseUrl, runtime.endpoint];
1454
- for (const candidate of candidates) {
1455
- if (typeof candidate === 'string' && candidate.trim()) {
1456
- return candidate.trim();
1457
- }
1458
- }
1459
- return undefined;
265
+ return normalizeRuntimeBaseUrl(this, runtime);
1460
266
  }
1461
267
  async resolveRuntimeAuth(runtime) {
1462
- const auth = runtime.auth || { type: 'apikey' };
1463
- const authRecord = auth;
1464
- const authType = this.normalizeAuthType(auth.type);
1465
- const rawType = typeof auth.rawType === 'string' ? auth.rawType.trim().toLowerCase() : '';
1466
- const pickString = (...candidates) => {
1467
- for (const candidate of candidates) {
1468
- if (typeof candidate === 'string') {
1469
- const trimmed = candidate.trim();
1470
- if (trimmed) {
1471
- return trimmed;
1472
- }
1473
- }
1474
- }
1475
- return undefined;
1476
- };
1477
- const pickStringArray = (value) => {
1478
- if (!value) {
1479
- return undefined;
1480
- }
1481
- if (Array.isArray(value)) {
1482
- const normalized = value
1483
- .map((item) => pickString(item))
1484
- .filter((item) => typeof item === 'string');
1485
- return normalized.length ? normalized : undefined;
1486
- }
1487
- if (typeof value === 'string' && value.trim()) {
1488
- const normalized = value
1489
- .split(/[,\s]+/)
1490
- .map((item) => item.trim())
1491
- .filter(Boolean);
1492
- return normalized.length ? normalized : undefined;
1493
- }
1494
- return undefined;
1495
- };
1496
- if (authType === 'apikey') {
1497
- if (rawType === 'iflow-cookie') {
1498
- return { ...auth, type: 'apikey', rawType: auth.rawType ?? 'iflow-cookie' };
1499
- }
1500
- if (rawType === 'deepseek-account') {
1501
- // Compatibility note:
1502
- // legacy deepseek configs may still carry value/secretRef/mobile/password/clientId/clientSecret.
1503
- // Runtime auth for deepseek-account is tokenFile(+alias)-only; we strip legacy fields instead of
1504
- // failing init so existing deployments can migrate without downtime.
1505
- const tokenFile = pickString(authRecord.tokenFile, authRecord.token_file);
1506
- const accountAlias = pickString(authRecord.accountAlias, authRecord.account_alias, runtime.keyAlias);
1507
- return {
1508
- type: 'apikey',
1509
- rawType: auth.rawType ?? 'deepseek-account',
1510
- value: '',
1511
- ...(accountAlias ? { accountAlias } : {}),
1512
- ...(tokenFile ? { tokenFile } : {})
1513
- };
1514
- }
1515
- const value = await this.resolveApiKeyValue(runtime, auth);
1516
- return { ...auth, type: 'apikey', value };
1517
- }
1518
- const resolved = {
1519
- type: 'oauth',
1520
- secretRef: auth.secretRef,
1521
- value: auth.value,
1522
- oauthProviderId: auth.oauthProviderId,
1523
- rawType: auth.rawType,
1524
- tokenFile: pickString(authRecord.tokenFile, authRecord.token_file),
1525
- tokenUrl: pickString(authRecord.tokenUrl, authRecord.token_url),
1526
- deviceCodeUrl: pickString(authRecord.deviceCodeUrl, authRecord.device_code_url),
1527
- clientId: pickString(authRecord.clientId, authRecord.client_id),
1528
- clientSecret: pickString(authRecord.clientSecret, authRecord.client_secret),
1529
- authorizationUrl: pickString(authRecord.authorizationUrl, authRecord.authorization_url, authRecord.authUrl),
1530
- userInfoUrl: pickString(authRecord.userInfoUrl, authRecord.user_info_url),
1531
- refreshUrl: pickString(authRecord.refreshUrl, authRecord.refresh_url),
1532
- scopes: pickStringArray(authRecord.scopes ?? authRecord.scope)
1533
- };
1534
- let tokenFile = resolved.tokenFile;
1535
- if (!tokenFile && typeof auth.secretRef === 'string' && auth.secretRef.trim()) {
1536
- tokenFile = await this.resolveSecretValue(auth.secretRef.trim());
1537
- }
1538
- resolved.tokenFile = tokenFile;
1539
- return resolved;
268
+ return await resolveRuntimeAuth(this, runtime);
1540
269
  }
1541
270
  async resolveApiKeyValue(runtime, auth) {
1542
- const inline = typeof auth?.value === 'string' ? auth.value.trim() : '';
1543
- if (inline) {
1544
- if (this.isSafeSecretReference(inline)) {
1545
- return await this.resolveSecretValue(inline);
1546
- }
1547
- return inline;
1548
- }
1549
- const rawSecretRef = typeof auth?.secretRef === 'string' ? auth.secretRef.trim() : '';
1550
- // llmswitch-core may populate secretRef as a stable signature (e.g. "provider.alias").
1551
- // Only treat it as a resolvable secret reference when it matches our safe reference grammar.
1552
- if (rawSecretRef && this.isSafeSecretReference(rawSecretRef)) {
1553
- const resolved = await this.resolveSecretValue(rawSecretRef);
1554
- if (resolved) {
1555
- return resolved;
1556
- }
1557
- }
1558
- // Local OpenAI-compatible endpoints (e.g., LM Studio) may not require auth.
1559
- // Keep fail-fast for remote providers by only allowing empty apiKey when baseURL is local.
1560
- const baseURL = typeof runtime?.baseURL === 'string'
1561
- ? String(runtime.baseURL).trim()
1562
- : typeof runtime?.baseUrl === 'string'
1563
- ? String(runtime.baseUrl).trim()
1564
- : typeof runtime?.endpoint === 'string'
1565
- ? String(runtime.endpoint).trim()
1566
- : '';
1567
- if (this.isLocalBaseUrl(baseURL)) {
1568
- return '';
1569
- }
1570
- throw new Error(`Provider runtime "${runtime.runtimeKey || runtime.providerId}" missing API key`);
271
+ return await resolveApiKeyValue(this, runtime, auth);
1571
272
  }
1572
273
  isLocalBaseUrl(value) {
1573
- const raw = typeof value === 'string' ? value.trim() : '';
1574
- if (!raw) {
1575
- return false;
1576
- }
1577
- try {
1578
- const url = new URL(raw);
1579
- const host = String(url.hostname || '').trim().toLowerCase();
1580
- return (host === 'localhost' ||
1581
- host === '127.0.0.1' ||
1582
- host === '0.0.0.0' ||
1583
- host === '::1' ||
1584
- host === '::ffff:127.0.0.1');
1585
- }
1586
- catch {
1587
- const lower = raw.toLowerCase();
1588
- return (lower.includes('localhost') ||
1589
- lower.includes('127.0.0.1') ||
1590
- lower.includes('0.0.0.0') ||
1591
- lower.includes('[::1]'));
1592
- }
274
+ return isLocalBaseUrl(this, value);
1593
275
  }
1594
276
  async disposeProviders() {
1595
- const handles = Array.from(this.providerHandles.values());
1596
- await Promise.all(handles.map(async (handle) => {
1597
- try {
1598
- await handle.instance.cleanup();
1599
- }
1600
- catch {
1601
- // ignore cleanup errors
1602
- }
1603
- }));
1604
- this.providerHandles.clear();
1605
- ProviderFactory.clearInstanceCache?.();
277
+ await disposeProviders(this);
1606
278
  }
1607
279
  async executePipeline(input) {
1608
- if (!this.isPipelineReady()) {
1609
- throw new Error('Hub pipeline runtime is not initialized');
1610
- }
1611
- // Stats must remain stable across provider retries and requestId enhancements.
1612
- const statsRequestId = input.requestId;
1613
- this.stats.recordRequestStart(statsRequestId);
1614
- const initialMetadata = this.buildRequestMetadata(input);
1615
- const providerRequestId = input.requestId;
1616
- const clientRequestId = typeof initialMetadata.clientRequestId === 'string' && initialMetadata.clientRequestId.trim()
1617
- ? initialMetadata.clientRequestId.trim()
1618
- : providerRequestId;
1619
- this.logStage('request.received', providerRequestId, {
1620
- endpoint: input.entryEndpoint,
1621
- stream: initialMetadata.stream === true
1622
- });
1623
- try {
1624
- const headerUa = (typeof input.headers?.['user-agent'] === 'string' && input.headers['user-agent']) ||
1625
- (typeof input.headers?.['User-Agent'] === 'string' && input.headers['User-Agent']);
1626
- const headerOriginator = (typeof input.headers?.['originator'] === 'string' && input.headers['originator']) ||
1627
- (typeof input.headers?.['Originator'] === 'string' && input.headers['Originator']);
1628
- await writeClientSnapshot({
1629
- entryEndpoint: input.entryEndpoint,
1630
- requestId: input.requestId,
1631
- headers: asRecord(input.headers),
1632
- body: input.body,
1633
- metadata: {
1634
- ...initialMetadata,
1635
- userAgent: headerUa,
1636
- clientOriginator: headerOriginator
1637
- }
1638
- });
1639
- }
1640
- catch {
1641
- // snapshot failure should not block request path
1642
- }
1643
- const pipelineLabel = 'hub';
1644
- const iterationMetadata = initialMetadata;
1645
- // _followupTriggered = false;
1646
- // 单次 HTTP 请求内允许多次 failover(不在 Provider 层做重试):
1647
- // - 让 VirtualRouter 根据 excludedProviderKeys 跳过失败目标
1648
- // - 避免客户端“一次就断”导致对话破裂(尤其是 429 / prompt too long 等可恢复错误)
1649
- // - 通过 env 允许按部署/客户端调整:ROUTECODEX_MAX_PROVIDER_ATTEMPTS / RCC_MAX_PROVIDER_ATTEMPTS
1650
- let maxAttempts = resolveMaxProviderAttempts();
1651
- let attempt = 0;
1652
- let firstError = null;
1653
- const originalBodySnapshot = this.cloneRequestPayload(input.body);
1654
- const excludedProviderKeys = new Set(this.startupExcludedProviderKeys);
1655
- let initialRoutePool = null;
1656
- while (attempt < maxAttempts) {
1657
- attempt += 1;
1658
- // 每次尝试前重置请求 body,避免上一轮 HubPipeline 的就地改写导致
1659
- // 第二轮出现 ChatEnvelopeValidationError(messages_missing) 之类的问题。
1660
- if (originalBodySnapshot && typeof originalBodySnapshot === 'object') {
1661
- const cloned = this.cloneRequestPayload(originalBodySnapshot) ??
1662
- { ...originalBodySnapshot };
1663
- input.body = cloned;
1664
- }
1665
- // 为本轮构建独立的 metadata 视图,并注入当前已排除的 providerKey 集合,
1666
- // 让 VirtualRouter 在同一 HTTP 请求内跳过已经 429 过的 key。
1667
- const metadataForIteration = {
1668
- ...iterationMetadata,
1669
- excludedProviderKeys: Array.from(excludedProviderKeys)
1670
- };
1671
- this.logStage(`${pipelineLabel}.start`, providerRequestId, {
1672
- endpoint: input.entryEndpoint,
1673
- stream: metadataForIteration.stream
1674
- });
1675
- const originalRequestSnapshot = this.cloneRequestPayload(input.body);
1676
- let pipelineResult;
1677
- try {
1678
- pipelineResult = await this.runHubPipeline(input, metadataForIteration);
1679
- }
1680
- catch (pipelineError) {
1681
- const pipelineErrorCode = typeof pipelineError.code === 'string'
1682
- ? String(pipelineError.code).trim()
1683
- : '';
1684
- const pipelineErrorMessage = pipelineError instanceof Error
1685
- ? pipelineError.message
1686
- : String(pipelineError ?? 'Unknown error');
1687
- const isPoolExhaustedError = pipelineErrorCode === 'PROVIDER_NOT_AVAILABLE' ||
1688
- pipelineErrorCode === 'ERR_NO_PROVIDER_TARGET' ||
1689
- /all providers unavailable/i.test(pipelineErrorMessage) ||
1690
- /virtual router did not produce a provider target/i.test(pipelineErrorMessage);
1691
- if (firstError && isPoolExhaustedError) {
1692
- throw firstError;
1693
- }
1694
- throw pipelineError;
1695
- }
1696
- const pipelineMetadata = pipelineResult.metadata ?? {};
1697
- const mergedMetadata = { ...metadataForIteration, ...pipelineMetadata };
1698
- this.logStage(`${pipelineLabel}.completed`, providerRequestId, {
1699
- route: pipelineResult.routingDecision?.routeName,
1700
- target: pipelineResult.target?.providerKey
1701
- });
1702
- if (!initialRoutePool && Array.isArray(pipelineResult.routingDecision?.pool)) {
1703
- initialRoutePool = [...pipelineResult.routingDecision.pool];
1704
- }
1705
- const providerPayload = pipelineResult.providerPayload;
1706
- const target = pipelineResult.target;
1707
- if (!providerPayload || !target?.providerKey) {
1708
- throw Object.assign(new Error('Virtual router did not produce a provider target'), {
1709
- code: 'ERR_NO_PROVIDER_TARGET',
1710
- requestId: input.requestId
1711
- });
1712
- }
1713
- // Ensure response-side conversion has access to route-selected target metadata (compat profiles, etc.).
1714
- // This is an execution metadata carrier only; tool/routing semantics still live in llmswitch-core.
1715
- if (!mergedMetadata.target) {
1716
- mergedMetadata.target = target;
1717
- }
1718
- if (!mergedMetadata.compatibilityProfile && typeof target.compatibilityProfile === 'string' && target.compatibilityProfile.trim()) {
1719
- mergedMetadata.compatibilityProfile = target.compatibilityProfile.trim();
1720
- }
1721
- const runtimeKey = target.runtimeKey || this.providerKeyToRuntimeKey.get(target.providerKey);
1722
- if (!runtimeKey) {
1723
- const error = Object.assign(new Error(`Runtime for provider ${target.providerKey} not initialized`), {
1724
- code: 'ERR_RUNTIME_NOT_FOUND',
1725
- requestId: input.requestId,
1726
- retryable: true
1727
- });
1728
- try {
1729
- const { emitProviderError } = await import('../../../providers/core/utils/provider-error-reporter.js');
1730
- emitProviderError({
1731
- error,
1732
- stage: 'provider.runtime.resolve',
1733
- runtime: {
1734
- requestId: input.requestId,
1735
- providerKey: target.providerKey,
1736
- providerId: target.providerKey.split('.')[0],
1737
- providerType: String(target.providerType || 'unknown'),
1738
- providerProtocol: String(target.outboundProfile || ''),
1739
- routeName: pipelineResult.routingDecision?.routeName,
1740
- pipelineId: target.providerKey,
1741
- target
1742
- },
1743
- dependencies: this.getModuleDependencies(),
1744
- recoverable: false,
1745
- affectsHealth: true,
1746
- details: {
1747
- reason: 'runtime_not_initialized',
1748
- providerKey: target.providerKey
1749
- }
1750
- });
1751
- }
1752
- catch {
1753
- // best-effort
1754
- }
1755
- if (!firstError) {
1756
- firstError = error;
1757
- }
1758
- excludedProviderKeys.add(target.providerKey);
1759
- this.logStage('provider.retry', input.requestId, {
1760
- providerKey: target.providerKey,
1761
- attempt,
1762
- nextAttempt: attempt + 1,
1763
- excluded: Array.from(excludedProviderKeys),
1764
- reason: 'runtime_not_initialized'
1765
- });
1766
- continue;
1767
- }
1768
- const handle = this.providerHandles.get(runtimeKey);
1769
- if (!handle) {
1770
- const initError = this.providerRuntimeInitErrors.get(runtimeKey);
1771
- const error = Object.assign(new Error(initError
1772
- ? `Provider runtime ${runtimeKey} failed to initialize: ${initError.message}`
1773
- : `Provider runtime ${runtimeKey} not found`), {
1774
- code: 'ERR_PROVIDER_NOT_FOUND',
1775
- requestId: input.requestId,
1776
- retryable: true
1777
- });
1778
- try {
1779
- const { emitProviderError } = await import('../../../providers/core/utils/provider-error-reporter.js');
1780
- emitProviderError({
1781
- error: initError ?? error,
1782
- stage: 'provider.runtime.resolve',
1783
- runtime: {
1784
- requestId: input.requestId,
1785
- providerKey: target.providerKey,
1786
- providerId: target.providerKey.split('.')[0],
1787
- providerType: String(target.providerType || 'unknown'),
1788
- providerProtocol: String(target.outboundProfile || ''),
1789
- routeName: pipelineResult.routingDecision?.routeName,
1790
- pipelineId: target.providerKey,
1791
- runtimeKey,
1792
- target
1793
- },
1794
- dependencies: this.getModuleDependencies(),
1795
- recoverable: false,
1796
- affectsHealth: true,
1797
- details: {
1798
- reason: 'runtime_handle_missing',
1799
- providerKey: target.providerKey,
1800
- runtimeKey
1801
- }
1802
- });
1803
- }
1804
- catch {
1805
- // best-effort
1806
- }
1807
- if (!firstError) {
1808
- firstError = error;
1809
- }
1810
- excludedProviderKeys.add(target.providerKey);
1811
- this.logStage('provider.retry', input.requestId, {
1812
- providerKey: target.providerKey,
1813
- attempt,
1814
- nextAttempt: attempt + 1,
1815
- excluded: Array.from(excludedProviderKeys),
1816
- reason: 'provider_runtime_missing'
1817
- });
1818
- continue;
1819
- }
1820
- const providerProtocol = target.outboundProfile ||
1821
- handle.providerProtocol;
1822
- const metadataModel = mergedMetadata?.target && typeof mergedMetadata.target === 'object'
1823
- ? mergedMetadata.target.clientModelId
1824
- : undefined;
1825
- const rawModel = this.extractProviderModel(providerPayload) ||
1826
- (typeof metadataModel === 'string' ? metadataModel : undefined);
1827
- const providerIdToken = target.providerKey || handle.providerId || runtimeKey;
1828
- if (!providerIdToken) {
1829
- throw Object.assign(new Error('Provider identifier missing for request'), {
1830
- code: 'ERR_PROVIDER_ID_MISSING',
1831
- requestId: providerRequestId
1832
- });
1833
- }
1834
- const enhancedRequestId = enhanceProviderRequestId(providerRequestId, {
1835
- entryEndpoint: input.entryEndpoint,
1836
- providerId: providerIdToken,
1837
- model: rawModel
1838
- });
1839
- // /v1/responses tool loop depends on a stable requestId mapping between:
1840
- // - inbound conversion capture (uses the initial requestId), and
1841
- // - outbound response record (may run after requestId enhancement).
1842
- // Enhancement happens for all providers, so rebind must be keyed off the client endpoint,
1843
- // not the providerProtocol (which can be gemini-chat/anthropic-messages/etc.).
1844
- if (String(input.entryEndpoint || '').startsWith('/v1/responses')) {
1845
- try {
1846
- await rebindResponsesConversationRequestId(providerRequestId, enhancedRequestId);
1847
- }
1848
- catch {
1849
- /* ignore rebind failures */
1850
- }
1851
- }
1852
- if (enhancedRequestId !== input.requestId) {
1853
- input.requestId = enhancedRequestId;
1854
- }
1855
- mergedMetadata.clientRequestId = clientRequestId;
1856
- const providerModel = rawModel;
1857
- const providerLabel = this.buildProviderLabel(target.providerKey, providerModel);
1858
- this.logStage('provider.prepare', input.requestId, {
1859
- providerKey: target.providerKey,
1860
- runtimeKey,
1861
- protocol: providerProtocol,
1862
- providerType: handle.providerType,
1863
- providerFamily: handle.providerFamily,
1864
- model: providerModel,
1865
- providerLabel
1866
- });
1867
- attachProviderRuntimeMetadata(providerPayload, {
1868
- requestId: input.requestId,
1869
- providerId: handle.providerId,
1870
- providerKey: target.providerKey,
1871
- providerType: handle.providerType,
1872
- providerFamily: handle.providerFamily,
1873
- providerProtocol,
1874
- pipelineId: target.providerKey,
1875
- routeName: pipelineResult.routingDecision?.routeName,
1876
- runtimeKey,
1877
- target,
1878
- metadata: mergedMetadata
1879
- });
1880
- this.stats.bindProvider(statsRequestId, {
1881
- providerKey: target.providerKey,
1882
- providerType: handle.providerType,
1883
- model: providerModel
1884
- });
1885
- this.logStage('provider.send.start', input.requestId, {
1886
- providerKey: target.providerKey,
1887
- runtimeKey,
1888
- protocol: providerProtocol,
1889
- providerType: handle.providerType,
1890
- providerFamily: handle.providerFamily,
1891
- model: providerModel,
1892
- providerLabel
1893
- });
1894
- try {
1895
- const providerResponse = await handle.instance.processIncoming(providerPayload);
1896
- const responseStatus = this.extractResponseStatus(providerResponse);
1897
- this.logStage('provider.send.completed', input.requestId, {
1898
- providerKey: target.providerKey,
1899
- status: responseStatus,
1900
- providerType: handle.providerType,
1901
- providerFamily: handle.providerFamily,
1902
- model: providerModel,
1903
- providerLabel
1904
- });
1905
- const wantsStreamBase = Boolean(input.metadata?.inboundStream ?? input.metadata?.stream);
1906
- const normalized = this.normalizeProviderResponse(providerResponse);
1907
- const pipelineProcessed = pipelineResult.processedRequest;
1908
- const pipelineStandardized = pipelineResult.standardizedRequest;
1909
- const requestSemantics = pipelineProcessed && typeof pipelineProcessed === 'object' && typeof pipelineProcessed.semantics === 'object'
1910
- ? pipelineProcessed.semantics
1911
- : pipelineStandardized && typeof pipelineStandardized === 'object' && typeof pipelineStandardized.semantics === 'object'
1912
- ? pipelineStandardized.semantics
1913
- : undefined;
1914
- const converted = await this.convertProviderResponseIfNeeded({
1915
- entryEndpoint: input.entryEndpoint,
1916
- providerType: handle.providerType,
1917
- requestId: input.requestId,
1918
- wantsStream: wantsStreamBase,
1919
- originalRequest: originalRequestSnapshot,
1920
- requestSemantics: requestSemantics,
1921
- processMode: pipelineResult.processMode,
1922
- response: normalized,
1923
- pipelineMetadata: mergedMetadata
1924
- });
1925
- // Treat upstream 429 as provider failure across protocols to avoid
1926
- // silently returning success and to let Virtual Router failover to other candidates.
1927
- // Keep existing Gemini compatibility behavior for 400/4xx thoughtSignature-like failures.
1928
- const convertedStatus = typeof converted.status === 'number' ? converted.status : undefined;
1929
- const isGlobalRetryable429 = convertedStatus === 429;
1930
- const isGeminiCompatFailure = typeof convertedStatus === 'number' &&
1931
- convertedStatus >= 400 &&
1932
- (isAntigravityProviderKey(target.providerKey) ||
1933
- (typeof target.providerKey === 'string' && target.providerKey.startsWith('gemini-cli.'))) &&
1934
- providerProtocol === 'gemini-chat';
1935
- if (isGlobalRetryable429 || isGeminiCompatFailure) {
1936
- const bodyForError = converted.body && typeof converted.body === 'object'
1937
- ? converted.body
1938
- : undefined;
1939
- const errMsg = bodyForError && bodyForError.error && typeof bodyForError.error === 'object'
1940
- ? String(bodyForError.error.message || bodyForError.error || '')
1941
- : '';
1942
- const statusCode = typeof convertedStatus === 'number' ? convertedStatus : 500;
1943
- const errorToThrow = new Error(errMsg && errMsg.trim().length ? errMsg : `HTTP ${statusCode}`);
1944
- errorToThrow.statusCode = statusCode;
1945
- errorToThrow.status = statusCode;
1946
- errorToThrow.response = { data: bodyForError };
1947
- try {
1948
- const { emitProviderError } = await import('../../../providers/core/utils/provider-error-reporter.js');
1949
- emitProviderError({
1950
- error: errorToThrow,
1951
- stage: 'provider.http',
1952
- runtime: {
1953
- requestId: input.requestId,
1954
- providerKey: target.providerKey,
1955
- providerId: handle.providerId,
1956
- providerType: handle.providerType,
1957
- providerFamily: handle.providerFamily,
1958
- providerProtocol,
1959
- routeName: pipelineResult.routingDecision?.routeName,
1960
- pipelineId: target.providerKey,
1961
- target,
1962
- runtimeKey
1963
- },
1964
- dependencies: this.getModuleDependencies(),
1965
- statusCode,
1966
- recoverable: statusCode === 429,
1967
- affectsHealth: true,
1968
- details: {
1969
- source: 'converted_response_status',
1970
- convertedStatus: statusCode,
1971
- wrappedErrorResponse: true
1972
- }
1973
- });
1974
- }
1975
- catch {
1976
- // best-effort; never block retry/failover path
1977
- }
1978
- throw errorToThrow;
1979
- }
1980
- const usage = this.extractUsageFromResult(converted, mergedMetadata);
1981
- // QuotaManager listens to provider error/success events; avoid duplicating accounting here.
1982
- this.stats.recordCompletion(statsRequestId, { usage, error: false });
1983
- // Notify llmswitch-core about successful completion so session-scoped routing state
1984
- // (e.g. Antigravity alias bindings) can be committed only after the first success.
1985
- try {
1986
- const center = await getProviderSuccessCenter().catch(() => null);
1987
- const sessionId = typeof mergedMetadata.sessionId === 'string' && mergedMetadata.sessionId.trim()
1988
- ? mergedMetadata.sessionId.trim()
1989
- : undefined;
1990
- const conversationId = typeof mergedMetadata.conversationId === 'string' && mergedMetadata.conversationId.trim()
1991
- ? mergedMetadata.conversationId.trim()
1992
- : undefined;
1993
- center?.emit({
1994
- runtime: {
1995
- requestId: input.requestId,
1996
- routeName: pipelineResult.routingDecision?.routeName,
1997
- providerKey: target.providerKey,
1998
- providerId: handle.providerId,
1999
- providerType: handle.providerType,
2000
- providerProtocol,
2001
- pipelineId: target.providerKey,
2002
- target
2003
- },
2004
- timestamp: Date.now(),
2005
- metadata: {
2006
- ...(sessionId ? { sessionId } : {}),
2007
- ...(conversationId ? { conversationId } : {})
2008
- }
2009
- });
2010
- }
2011
- catch {
2012
- // best-effort: must never affect request path
2013
- }
2014
- // 回传 session_id 和 conversation_id 到响应头(如果存在)
2015
- const sessionId = typeof mergedMetadata.sessionId === "string" && mergedMetadata.sessionId.trim()
2016
- ? mergedMetadata.sessionId.trim()
2017
- : undefined;
2018
- let conversationId = typeof mergedMetadata.conversationId === "string" && mergedMetadata.conversationId.trim()
2019
- ? mergedMetadata.conversationId.trim()
2020
- : undefined;
2021
- // 对称补齐:如果只有 session_id,则回传 conversation_id=session_id
2022
- if (!conversationId && sessionId) {
2023
- conversationId = sessionId;
2024
- }
2025
- if (sessionId || conversationId) {
2026
- if (!converted.headers) {
2027
- converted.headers = {};
2028
- }
2029
- if (sessionId && !converted.headers["session_id"]) {
2030
- converted.headers["session_id"] = sessionId;
2031
- }
2032
- if (conversationId && !converted.headers["conversation_id"]) {
2033
- converted.headers["conversation_id"] = conversationId;
2034
- }
2035
- }
2036
- return converted;
2037
- }
2038
- catch (error) {
2039
- this.logStage('provider.send.error', input.requestId, {
2040
- providerKey: target.providerKey,
2041
- message: error instanceof Error ? error.message : String(error ?? 'Unknown error'),
2042
- providerType: handle.providerType,
2043
- providerFamily: handle.providerFamily,
2044
- model: providerModel,
2045
- providerLabel
2046
- });
2047
- if (isAntigravityProviderKey(target.providerKey) && extractStatusCodeFromError(error) === 429) {
2048
- maxAttempts = Math.max(maxAttempts, resolveAntigravityMaxProviderAttempts());
2049
- }
2050
- // QuotaManager listens to provider error events emitted by providers.
2051
- if (!firstError) {
2052
- firstError = error;
2053
- }
2054
- const shouldRetry = attempt < maxAttempts && shouldRetryProviderError(error);
2055
- if (!shouldRetry) {
2056
- this.stats.recordCompletion(statsRequestId, { error: true });
2057
- throw error;
2058
- }
2059
- // Record this failed provider attempt even if the overall request succeeds later via failover.
2060
- this.stats.recordCompletion(statsRequestId, { error: true });
2061
- const singleProviderPool = Boolean(initialRoutePool && initialRoutePool.length === 1 && initialRoutePool[0] === target.providerKey);
2062
- if (singleProviderPool) {
2063
- if (isNetworkTransportError(error)) {
2064
- await waitBeforeRetry(error);
2065
- }
2066
- }
2067
- else if (target.providerKey) {
2068
- excludedProviderKeys.add(target.providerKey);
2069
- }
2070
- this.logStage('provider.retry', input.requestId, {
2071
- providerKey: target.providerKey,
2072
- attempt,
2073
- nextAttempt: attempt + 1,
2074
- excluded: Array.from(excludedProviderKeys),
2075
- reason: describeRetryReason(error)
2076
- });
2077
- continue;
2078
- }
2079
- }
2080
- // best-effort: if failover attempt could not produce a successful response,
2081
- // return the original error (avoid masking 429 with PROVIDER_NOT_AVAILABLE etc.).
2082
- this.stats.recordCompletion(statsRequestId, { error: true });
2083
- throw firstError ?? new Error('Provider execution failed without response');
2084
- }
2085
- async runHubPipeline(input, metadata) {
2086
- if (!this.hubPipeline) {
2087
- throw new Error('Hub pipeline runtime is not initialized');
2088
- }
2089
- const payload = asRecord(input.body);
2090
- const isInternalFollowup = asRecord(metadata.__rt)?.serverToolFollowup === true;
2091
- const wantsShadowCompare = !isInternalFollowup && shouldRunHubShadowCompare(this.hubShadowCompareConfig);
2092
- const pipelineInput = {
2093
- ...input,
2094
- id: input.requestId,
2095
- endpoint: input.entryEndpoint,
2096
- metadata: {
2097
- ...metadata,
2098
- logger: this.coloredLogger,
2099
- ...(wantsShadowCompare
2100
- ? { __hubShadowCompare: { baselineMode: this.hubShadowCompareConfig.baselineMode } }
2101
- : {})
2102
- },
2103
- payload
2104
- };
2105
- const result = await this.hubPipeline.execute(pipelineInput);
2106
- if (!result.providerPayload || !result.target?.providerKey) {
2107
- throw Object.assign(new Error('Virtual router did not produce a provider target'), {
2108
- code: 'ERR_NO_PROVIDER_TARGET',
2109
- requestId: input.requestId
2110
- });
2111
- }
2112
- const processMode = result.metadata?.processMode ?? 'chat';
2113
- const resultRecord = result;
2114
- const derivedRequestId = typeof resultRecord.requestId === 'string'
2115
- ? resultRecord.requestId
2116
- : input.requestId;
2117
- const llmsEngineShadowEnabled = !isInternalFollowup &&
2118
- isLlmsEngineShadowEnabledForSubpath(this.llmsEngineShadowConfig, 'conversion/hub/pipeline');
2119
- if (llmsEngineShadowEnabled) {
2120
- // Fail fast: if shadow is enabled for this module, engine core must be available.
2121
- await this.ensureHubPipelineEngineShadow();
2122
- }
2123
- const wantsLlmsEngineShadow = llmsEngineShadowEnabled &&
2124
- shouldRunLlmsEngineShadowForSubpath(this.llmsEngineShadowConfig, 'conversion/hub/pipeline');
2125
- // Unified Hub Framework V1: runtime black-box shadow compare (baseline policy vs current policy).
2126
- // - baseline payload is computed in the SAME hub pipeline execution (single-pass)
2127
- // - only writes errorsample when diff exists
2128
- try {
2129
- const shadow = result.metadata?.hubShadowCompare;
2130
- const baselineProviderPayload = shadow && typeof shadow === 'object' && !Array.isArray(shadow)
2131
- ? shadow.baselineProviderPayload
2132
- : undefined;
2133
- if (wantsShadowCompare && baselineProviderPayload && typeof baselineProviderPayload === 'object') {
2134
- const entryEndpoint = String(input.entryEndpoint || '/v1/chat/completions');
2135
- const routeHint = typeof metadata.routeHint === 'string'
2136
- ? String(metadata.routeHint)
2137
- : undefined;
2138
- const excludedProviderKeys = Array.isArray(metadata.excludedProviderKeys)
2139
- ? metadata.excludedProviderKeys
2140
- : [];
2141
- const cloneJsonSafe = (value) => {
2142
- try {
2143
- return JSON.parse(JSON.stringify(value));
2144
- }
2145
- catch {
2146
- return value;
2147
- }
2148
- };
2149
- const candidateOut = {
2150
- providerPayload: cloneJsonSafe(result.providerPayload),
2151
- target: cloneJsonSafe(result.target),
2152
- metadata: {
2153
- entryEndpoint: result.metadata?.entryEndpoint,
2154
- providerProtocol: result.metadata?.providerProtocol,
2155
- processMode: result.metadata?.processMode,
2156
- stream: result.metadata?.stream,
2157
- routeHint: result.metadata?.routeHint
2158
- }
2159
- };
2160
- void (async () => {
2161
- try {
2162
- const baselineOut = {
2163
- providerPayload: cloneJsonSafe(baselineProviderPayload),
2164
- target: shadow && typeof shadow === 'object' && !Array.isArray(shadow) && shadow.baselineTarget
2165
- ? cloneJsonSafe(shadow.baselineTarget)
2166
- : cloneJsonSafe(result.target),
2167
- metadata: {
2168
- entryEndpoint: result.metadata?.entryEndpoint,
2169
- providerProtocol: result.metadata?.providerProtocol,
2170
- processMode: result.metadata?.processMode,
2171
- stream: result.metadata?.stream,
2172
- routeHint: result.metadata?.routeHint
2173
- }
2174
- };
2175
- await recordHubShadowCompareDiff({
2176
- requestId: derivedRequestId,
2177
- entryEndpoint,
2178
- routeHint,
2179
- excludedProviderKeys,
2180
- baselineMode: this.hubShadowCompareConfig.baselineMode,
2181
- candidateMode: typeof shadow?.candidateMode === 'string' ? shadow.candidateMode : (this.hubPolicyMode ?? undefined),
2182
- baselineOut,
2183
- candidateOut
2184
- });
2185
- }
2186
- catch (error) {
2187
- // eslint-disable-next-line no-console
2188
- console.error('[unified-hub-shadow-runtime] baseline compare failed:', error);
2189
- }
2190
- })();
2191
- }
2192
- }
2193
- catch {
2194
- // best-effort only
2195
- }
2196
- // llms-engine: runtime black-box shadow compare (TS vs engine) for hub pipeline.
2197
- if (wantsLlmsEngineShadow) {
2198
- const cloneJsonSafe = (value) => {
2199
- try {
2200
- return JSON.parse(JSON.stringify(value));
2201
- }
2202
- catch {
2203
- return value;
2204
- }
2205
- };
2206
- const entryEndpoint = String(input.entryEndpoint || '/v1/chat/completions');
2207
- const routeHint = typeof metadata.routeHint === 'string'
2208
- ? String(metadata.routeHint)
2209
- : undefined;
2210
- const baselineOut = {
2211
- providerPayload: cloneJsonSafe(result.providerPayload),
2212
- target: cloneJsonSafe(result.target),
2213
- metadata: {
2214
- entryEndpoint: result.metadata?.entryEndpoint,
2215
- providerProtocol: result.metadata?.providerProtocol,
2216
- processMode: result.metadata?.processMode,
2217
- stream: result.metadata?.stream,
2218
- routeHint: result.metadata?.routeHint
2219
- }
2220
- };
2221
- void (async () => {
2222
- try {
2223
- const shadowPipeline = await this.ensureHubPipelineEngineShadow();
2224
- const shadowRequestId = `${input.requestId}__llms_engine_shadow`;
2225
- const baseMeta = pipelineInput.metadata;
2226
- const shadowInput = {
2227
- ...pipelineInput,
2228
- id: shadowRequestId,
2229
- endpoint: input.entryEndpoint,
2230
- metadata: {
2231
- ...(baseMeta && typeof baseMeta === 'object' ? baseMeta : {}),
2232
- __llmsEngineShadow: { baselineRequestId: derivedRequestId, entryEndpoint, routeHint }
2233
- },
2234
- payload: cloneJsonSafe(payload)
2235
- };
2236
- const shadowResult = await shadowPipeline.execute(shadowInput);
2237
- if (!shadowResult?.providerPayload || !shadowResult?.target) {
2238
- return;
2239
- }
2240
- const candidateOut = {
2241
- providerPayload: cloneJsonSafe(shadowResult.providerPayload),
2242
- target: cloneJsonSafe(shadowResult.target),
2243
- metadata: {
2244
- entryEndpoint: shadowResult.metadata?.entryEndpoint,
2245
- providerProtocol: shadowResult.metadata?.providerProtocol,
2246
- processMode: shadowResult.metadata?.processMode,
2247
- stream: shadowResult.metadata?.stream,
2248
- routeHint: shadowResult.metadata?.routeHint
2249
- }
2250
- };
2251
- await recordLlmsEngineShadowDiff({
2252
- group: 'hub-pipeline',
2253
- requestId: derivedRequestId,
2254
- subpath: 'conversion/hub/pipeline',
2255
- baselineImpl: 'ts',
2256
- candidateImpl: 'engine',
2257
- baselineOut,
2258
- candidateOut
2259
- });
2260
- }
2261
- catch (error) {
2262
- // eslint-disable-next-line no-console
2263
- console.error('[llms-engine-shadow] hub pipeline shadow failed:', error);
2264
- }
2265
- })();
2266
- }
2267
- return {
2268
- requestId: derivedRequestId,
2269
- providerPayload: result.providerPayload,
2270
- standardizedRequest: result.standardizedRequest && typeof result.standardizedRequest === 'object'
2271
- ? result.standardizedRequest
2272
- : undefined,
2273
- processedRequest: result.processedRequest && typeof result.processedRequest === 'object'
2274
- ? result.processedRequest
2275
- : undefined,
2276
- target: result.target,
2277
- routingDecision: result.routingDecision ?? undefined,
2278
- processMode,
2279
- metadata: result.metadata ?? {}
2280
- };
2281
- }
2282
- buildRequestMetadata(input) {
2283
- const userMeta = asRecord(input.metadata);
2284
- const headers = asRecord(input.headers);
2285
- const inboundUserAgent = this.extractHeaderValue(headers, 'user-agent');
2286
- const inboundOriginator = this.extractHeaderValue(headers, 'originator');
2287
- const resolvedUserAgent = typeof userMeta.userAgent === 'string' && userMeta.userAgent.trim()
2288
- ? userMeta.userAgent.trim()
2289
- : inboundUserAgent;
2290
- const resolvedOriginator = typeof userMeta.clientOriginator === 'string' && userMeta.clientOriginator.trim()
2291
- ? userMeta.clientOriginator.trim()
2292
- : inboundOriginator;
2293
- const routeHint = this.extractRouteHint(input) ?? userMeta.routeHint;
2294
- const processMode = userMeta.processMode || 'chat';
2295
- const runtimeFromUser = asRecord(userMeta.runtime);
2296
- const llmsVersion = resolveLlmswitchCoreVersion();
2297
- const metadata = {
2298
- ...userMeta,
2299
- entryEndpoint: input.entryEndpoint,
2300
- processMode,
2301
- direction: 'request',
2302
- stage: 'inbound',
2303
- routeHint,
2304
- stream: userMeta.stream === true,
2305
- runtime: {
2306
- ...(runtimeFromUser ?? {}),
2307
- routecodex: {
2308
- version: buildInfo.version,
2309
- mode: buildInfo.mode
2310
- },
2311
- llmswitchCore: llmsVersion ? { version: llmsVersion } : undefined,
2312
- node: { version: process.version }
2313
- },
2314
- ...(resolvedUserAgent ? { userAgent: resolvedUserAgent } : {}),
2315
- ...(resolvedOriginator ? { clientOriginator: resolvedOriginator } : {})
2316
- };
2317
- // 将原始客户端请求头快照到 metadata.clientHeaders,便于 llmswitch-core
2318
- // 的 extractSessionIdentifiersFromMetadata 从中解析 session_id / conversation_id。
2319
- if (!metadata.clientHeaders && headers && Object.keys(headers).length) {
2320
- const clientHeaders = {};
2321
- for (const [key, value] of Object.entries(headers)) {
2322
- if (typeof value === 'string') {
2323
- const trimmed = value.trim();
2324
- if (trimmed) {
2325
- clientHeaders[key] = trimmed;
2326
- }
2327
- }
2328
- else if (Array.isArray(value) && value.length) {
2329
- const first = String(value[0]).trim();
2330
- if (first) {
2331
- clientHeaders[key] = first;
2332
- }
2333
- }
2334
- }
2335
- if (Object.keys(clientHeaders).length) {
2336
- metadata.clientHeaders = clientHeaders;
2337
- }
2338
- }
2339
- // 在 Host 入口统一解析会话标识,后续 HubPipeline / servertool 等模块仅依赖
2340
- // sessionId / conversationId 字段,不再重复解析 clientHeaders。
2341
- const identifiers = extractSessionIdentifiersFromMetadata(metadata);
2342
- if (identifiers.sessionId) {
2343
- metadata.sessionId = identifiers.sessionId;
2344
- }
2345
- if (identifiers.conversationId) {
2346
- metadata.conversationId = identifiers.conversationId;
2347
- }
2348
- return metadata;
2349
- }
2350
- extractHeaderValue(headers, name) {
2351
- if (!headers) {
2352
- return undefined;
2353
- }
2354
- const target = name.toLowerCase();
2355
- for (const [key, value] of Object.entries(headers)) {
2356
- if (key.toLowerCase() !== target) {
2357
- continue;
2358
- }
2359
- if (typeof value === 'string') {
2360
- return value.trim() || undefined;
2361
- }
2362
- if (Array.isArray(value) && value.length) {
2363
- return String(value[0]).trim() || undefined;
2364
- }
2365
- return undefined;
2366
- }
2367
- return undefined;
2368
- }
2369
- extractRouteHint(input) {
2370
- const header = input.headers['x-route-hint'];
2371
- if (typeof header === 'string' && header.trim()) {
2372
- return header.trim();
2373
- }
2374
- if (Array.isArray(header) && header[0]) {
2375
- return String(header[0]);
2376
- }
2377
- return undefined;
2378
- }
2379
- extractResponseStatus(response) {
2380
- if (!response || typeof response !== 'object') {
2381
- return undefined;
2382
- }
2383
- const candidate = response.status;
2384
- return typeof candidate === 'number' ? candidate : undefined;
2385
- }
2386
- normalizeProviderResponse(response) {
2387
- const status = this.extractResponseStatus(response);
2388
- const headers = this.normalizeProviderResponseHeaders(response && typeof response === 'object' ? response.headers : undefined);
2389
- const body = response && typeof response === 'object' && 'data' in response
2390
- ? response.data
2391
- : response;
2392
- return { status, headers, body };
2393
- }
2394
- normalizeProviderResponseHeaders(headers) {
2395
- if (!headers || typeof headers !== 'object') {
2396
- return undefined;
2397
- }
2398
- const normalized = {};
2399
- for (const [key, value] of Object.entries(headers)) {
2400
- if (typeof value === 'string') {
2401
- normalized[key.toLowerCase()] = value;
2402
- }
2403
- }
2404
- return Object.keys(normalized).length ? normalized : undefined;
2405
- }
2406
- extractUsageFromResult(result, metadata) {
2407
- const candidates = [];
2408
- if (metadata && typeof metadata === 'object') {
2409
- const bag = metadata;
2410
- if (bag.usage) {
2411
- candidates.push(bag.usage);
2412
- }
2413
- }
2414
- if (result.body && typeof result.body === 'object') {
2415
- const body = result.body;
2416
- if (body.usage) {
2417
- candidates.push(body.usage);
2418
- }
2419
- if (body.response && typeof body.response === 'object') {
2420
- const responseNode = body.response;
2421
- if (responseNode.usage) {
2422
- candidates.push(responseNode.usage);
2423
- }
2424
- }
2425
- }
2426
- for (const candidate of candidates) {
2427
- const normalized = this.normalizeUsage(candidate);
2428
- if (normalized) {
2429
- return normalized;
2430
- }
2431
- }
2432
- return undefined;
2433
- }
2434
- normalizeUsage(value) {
2435
- if (!value || typeof value !== 'object') {
2436
- return undefined;
2437
- }
2438
- const record = value;
2439
- const prompt = typeof record.prompt_tokens === 'number'
2440
- ? record.prompt_tokens
2441
- : typeof record.input_tokens === 'number'
2442
- ? record.input_tokens
2443
- : undefined;
2444
- const completion = typeof record.completion_tokens === 'number'
2445
- ? record.completion_tokens
2446
- : typeof record.output_tokens === 'number'
2447
- ? record.output_tokens
2448
- : undefined;
2449
- let total = typeof record.total_tokens === 'number'
2450
- ? record.total_tokens
2451
- : undefined;
2452
- if (total === undefined && prompt !== undefined && completion !== undefined) {
2453
- total = prompt + completion;
2454
- }
2455
- if (prompt === undefined && completion === undefined && total === undefined) {
2456
- return undefined;
2457
- }
2458
- return {
2459
- prompt_tokens: prompt,
2460
- completion_tokens: completion,
2461
- total_tokens: total
2462
- };
2463
- }
2464
- async convertProviderResponseIfNeeded(options) {
2465
- const body = options.response.body;
2466
- if (body && typeof body === 'object') {
2467
- const wrapperError = this.extractSseWrapperError(body);
2468
- if (wrapperError) {
2469
- const codeSuffix = wrapperError.errorCode ? ` [${wrapperError.errorCode}]` : '';
2470
- const error = new Error(`Upstream SSE error event${codeSuffix}: ${wrapperError.message}`);
2471
- error.code = 'SSE_DECODE_ERROR';
2472
- if (wrapperError.errorCode) {
2473
- error.upstreamCode = wrapperError.errorCode;
2474
- }
2475
- error.retryable = wrapperError.retryable;
2476
- if (wrapperError.retryable) {
2477
- error.status = 503;
2478
- error.statusCode = 503;
2479
- }
2480
- throw error;
2481
- }
2482
- }
2483
- if (options.processMode === 'passthrough' && !options.wantsStream) {
2484
- return options.response;
2485
- }
2486
- const entry = (options.entryEndpoint || '').toLowerCase();
2487
- const needsAnthropicConversion = entry.includes('/v1/messages');
2488
- const needsResponsesConversion = entry.includes('/v1/responses');
2489
- const needsChatConversion = entry.includes('/v1/chat/completions');
2490
- if (!needsAnthropicConversion && !needsResponsesConversion && !needsChatConversion) {
2491
- return options.response;
2492
- }
2493
- if (!body || typeof body !== 'object') {
2494
- return options.response;
2495
- }
2496
- try {
2497
- const providerProtocol = mapProviderProtocol(options.providerType);
2498
- const metadataBag = asRecord(options.pipelineMetadata);
2499
- const originalModelId = this.extractClientModelId(metadataBag, options.originalRequest);
2500
- const assignedModelId = typeof metadataBag?.assignedModelId === 'string'
2501
- ? String(metadataBag.assignedModelId)
2502
- : metadataBag &&
2503
- typeof metadataBag === 'object' &&
2504
- metadataBag.target &&
2505
- typeof metadataBag.target === 'object' &&
2506
- typeof metadataBag.target.modelId === 'string'
2507
- ? metadataBag.target.modelId
2508
- : typeof metadataBag?.modelId === 'string'
2509
- ? String(metadataBag.modelId)
2510
- : undefined;
2511
- // 以 HubPipeline metadata 为基础构建 AdapterContext,确保诸如
2512
- // capturedChatRequest / webSearch / routeHint 等字段在响应侧可见,
2513
- // 便于 llmswitch-core 内部实现 servertool/web_search 的第三跳。
2514
- const baseContext = {
2515
- ...(metadataBag ?? {})
2516
- };
2517
- if (baseContext.capturedChatRequest === undefined &&
2518
- options.originalRequest &&
2519
- typeof options.originalRequest === 'object' &&
2520
- !Array.isArray(options.originalRequest)) {
2521
- baseContext.capturedChatRequest = options.originalRequest;
2522
- }
2523
- // 将 HubPipeline metadata.routeName 映射为 AdapterContext.routeId,
2524
- // 便于 llmswitch-core 在第三跳中使用 routeHint 复用首次路由决策。
2525
- if (typeof metadataBag?.routeName === 'string') {
2526
- baseContext.routeId = metadataBag.routeName;
2527
- }
2528
- baseContext.requestId = options.requestId;
2529
- baseContext.entryEndpoint = options.entryEndpoint || entry;
2530
- baseContext.providerProtocol = providerProtocol;
2531
- baseContext.originalModelId = originalModelId;
2532
- if (assignedModelId && assignedModelId.trim()) {
2533
- baseContext.modelId = assignedModelId.trim();
2534
- }
2535
- const adapterContext = baseContext;
2536
- const compatProfile = typeof metadataBag?.compatibilityProfile === 'string'
2537
- ? String(metadataBag.compatibilityProfile)
2538
- : metadataBag &&
2539
- typeof metadataBag === 'object' &&
2540
- metadataBag.target &&
2541
- typeof metadataBag.target === 'object' &&
2542
- typeof metadataBag.target.compatibilityProfile === 'string'
2543
- ? metadataBag.target.compatibilityProfile
2544
- : undefined;
2545
- if (compatProfile && compatProfile.trim()) {
2546
- adapterContext.compatibilityProfile = compatProfile.trim();
2547
- }
2548
- const stageRecorder = await bridgeCreateSnapshotRecorder(adapterContext, typeof adapterContext.entryEndpoint === 'string'
2549
- ? adapterContext.entryEndpoint
2550
- : options.entryEndpoint || entry);
2551
- const providerInvoker = async (invokeOptions) => {
2552
- const runtimeKey = this.providerKeyToRuntimeKey.get(invokeOptions.providerKey) || invokeOptions.providerKey;
2553
- const handle = this.providerHandles.get(runtimeKey);
2554
- if (!handle) {
2555
- throw new Error(`Provider runtime ${runtimeKey} not found`);
2556
- }
2557
- const providerResponse = await handle.instance.processIncoming(invokeOptions.payload);
2558
- const normalized = this.normalizeProviderResponse(providerResponse);
2559
- const bodyPayload = normalized.body && typeof normalized.body === 'object'
2560
- ? normalized.body
2561
- : normalized;
2562
- return { providerResponse: bodyPayload };
2563
- };
2564
- const reenterPipeline = async (reenterOpts) => {
2565
- const nestedEntry = reenterOpts.entryEndpoint || options.entryEndpoint || entry;
2566
- const nestedExtra = asRecord(reenterOpts.metadata) ?? {};
2567
- // 基于首次 HubPipeline metadata + 调用方注入的 metadata 构建新的请求 metadata。
2568
- // 不在 Host 层编码 servertool/web_search 等语义,由 llmswitch-core 负责。
2569
- const nestedMetadata = {
2570
- ...(metadataBag ?? {}),
2571
- ...nestedExtra,
2572
- entryEndpoint: nestedEntry,
2573
- direction: 'request',
2574
- stage: 'inbound'
2575
- };
2576
- // servertool followup 是内部二跳请求:不应继承客户端 headers 偏好(尤其是 Accept),
2577
- // 否则会导致上游返回非 SSE 响应而被当作 SSE 解析,出现“空回复”。
2578
- // E1: merge internal runtime metadata carrier (`__rt`) instead of clobbering it.
2579
- try {
2580
- const baseRt = asRecord(metadataBag?.__rt) ?? {};
2581
- const extraRt = asRecord(nestedExtra?.__rt) ?? {};
2582
- if (Object.keys(baseRt).length || Object.keys(extraRt).length) {
2583
- nestedMetadata.__rt = { ...baseRt, ...extraRt };
2584
- }
2585
- }
2586
- catch {
2587
- // best-effort
2588
- }
2589
- if (asRecord(nestedMetadata.__rt)?.serverToolFollowup === true) {
2590
- delete nestedMetadata.clientHeaders;
2591
- delete nestedMetadata.clientRequestId;
2592
- }
2593
- const nestedInput = {
2594
- entryEndpoint: nestedEntry,
2595
- method: 'POST',
2596
- requestId: reenterOpts.requestId,
2597
- headers: {},
2598
- query: {},
2599
- body: reenterOpts.body,
2600
- metadata: nestedMetadata
2601
- };
2602
- const nestedResult = await this.executePipeline(nestedInput);
2603
- const nestedBody = nestedResult.body && typeof nestedResult.body === 'object'
2604
- ? nestedResult.body
2605
- : undefined;
2606
- return { body: nestedBody };
2607
- };
2608
- const converted = await bridgeConvertProviderResponse({
2609
- providerProtocol,
2610
- providerResponse: body,
2611
- context: adapterContext,
2612
- entryEndpoint: options.entryEndpoint || entry,
2613
- wantsStream: options.wantsStream,
2614
- requestSemantics: options.requestSemantics,
2615
- providerInvoker,
2616
- stageRecorder,
2617
- reenterPipeline
2618
- });
2619
- if (converted.__sse_responses) {
2620
- return {
2621
- ...options.response,
2622
- body: { __sse_responses: converted.__sse_responses }
2623
- };
2624
- }
2625
- return {
2626
- ...options.response,
2627
- body: converted.body ?? body
2628
- };
2629
- }
2630
- catch (error) {
2631
- const err = error;
2632
- const message = err instanceof Error ? err.message : String(err ?? 'Unknown error');
2633
- const errRecord = err;
2634
- const errCode = typeof errRecord.code === 'string' ? errRecord.code : undefined;
2635
- const errName = typeof errRecord.name === 'string' ? errRecord.name : undefined;
2636
- const statusCandidate = typeof errRecord.status === 'number'
2637
- ? errRecord.status
2638
- : typeof errRecord.statusCode === 'number'
2639
- ? errRecord.statusCode
2640
- : undefined;
2641
- const isSseDecodeError = errCode === 'SSE_DECODE_ERROR' ||
2642
- (errName === 'ProviderProtocolError' && message.toLowerCase().includes('sse'));
2643
- const isServerToolFollowupError = errCode === 'SERVERTOOL_FOLLOWUP_FAILED' ||
2644
- errCode === 'SERVERTOOL_EMPTY_FOLLOWUP' ||
2645
- (typeof errCode === 'string' && errCode.startsWith('SERVERTOOL_'));
2646
- const isProviderProtocolError = errName === 'ProviderProtocolError';
2647
- // If we need to stream a client response, conversion failures are fatal: there is no safe fallback
2648
- // that preserves protocol correctness.
2649
- const isStreamingConversion = Boolean(options.wantsStream && (needsAnthropicConversion || needsResponsesConversion || needsChatConversion));
2650
- if (isSseDecodeError || isServerToolFollowupError || isStreamingConversion || (isProviderProtocolError && typeof statusCandidate === 'number')) {
2651
- console.error('[RouteCodexHttpServer] Fatal conversion error, bubbling as HTTP error', error);
2652
- throw error;
2653
- }
2654
- console.error('[RouteCodexHttpServer] Failed to convert provider response via llmswitch-core', error);
2655
- return options.response;
2656
- }
2657
- }
2658
- extractSseWrapperError(payload) {
2659
- return this.findSseWrapperError(payload, 2);
2660
- }
2661
- findSseWrapperError(record, depth) {
2662
- if (!record || typeof record !== 'object' || depth < 0) {
2663
- return undefined;
2664
- }
2665
- if (record.mode === 'sse') {
2666
- const normalized = this.normalizeSseWrapperErrorValue(record.error, depth);
2667
- if (normalized) {
2668
- return normalized;
2669
- }
2670
- }
2671
- const nestedKeys = ['body', 'data', 'payload', 'response'];
2672
- for (const key of nestedKeys) {
2673
- const nested = record[key];
2674
- if (!nested || typeof nested !== 'object' || Array.isArray(nested)) {
2675
- continue;
2676
- }
2677
- const found = this.findSseWrapperError(nested, depth - 1);
2678
- if (found) {
2679
- return found;
2680
- }
2681
- }
2682
- return undefined;
2683
- }
2684
- normalizeSseWrapperErrorValue(value, depth) {
2685
- if (value === undefined || value === null || depth < 0) {
2686
- return undefined;
2687
- }
2688
- if (typeof value === 'string') {
2689
- const trimmed = value.trim();
2690
- if (!trimmed) {
2691
- return undefined;
2692
- }
2693
- if (depth > 0 && (trimmed.startsWith('{') || trimmed.startsWith('['))) {
2694
- try {
2695
- const parsed = JSON.parse(trimmed);
2696
- const parsedInfo = this.normalizeSseWrapperErrorValue(parsed, depth - 1);
2697
- if (parsedInfo) {
2698
- return parsedInfo;
2699
- }
2700
- }
2701
- catch {
2702
- // fallback to raw string
2703
- }
2704
- }
2705
- return {
2706
- message: trimmed,
2707
- retryable: isRetryableSseWrapperError(trimmed)
2708
- };
2709
- }
2710
- if (typeof value !== 'object' || Array.isArray(value)) {
2711
- return undefined;
2712
- }
2713
- const record = value;
2714
- const directMessage = firstNonEmptyString([
2715
- record.message,
2716
- record.error_message,
2717
- record.errorMessage
2718
- ]);
2719
- const directCode = firstNonEmptyString([
2720
- record.code,
2721
- record.error_code,
2722
- record.errorCode,
2723
- record.type
2724
- ]);
2725
- const directStatus = firstFiniteNumber([
2726
- record.status,
2727
- record.statusCode,
2728
- record.status_code,
2729
- record.http_status
2730
- ]);
2731
- if (depth > 0) {
2732
- for (const key of ['error', 'data', 'payload', 'details', 'body', 'response']) {
2733
- const nestedInfo = this.normalizeSseWrapperErrorValue(record[key], depth - 1);
2734
- if (nestedInfo) {
2735
- const mergedCode = nestedInfo.errorCode ?? directCode;
2736
- const retryable = nestedInfo.retryable || isRetryableSseWrapperError(nestedInfo.message, mergedCode, directStatus);
2737
- return {
2738
- message: nestedInfo.message,
2739
- ...(mergedCode ? { errorCode: mergedCode } : {}),
2740
- retryable
2741
- };
2742
- }
2743
- }
2744
- }
2745
- if (directMessage) {
2746
- return {
2747
- message: directMessage,
2748
- ...(directCode ? { errorCode: directCode } : {}),
2749
- retryable: isRetryableSseWrapperError(directMessage, directCode, directStatus)
2750
- };
2751
- }
2752
- try {
2753
- const serialized = JSON.stringify(record);
2754
- if (serialized && serialized !== '{}') {
2755
- return {
2756
- message: serialized,
2757
- ...(directCode ? { errorCode: directCode } : {}),
2758
- retryable: isRetryableSseWrapperError(serialized, directCode, directStatus)
2759
- };
2760
- }
2761
- }
2762
- catch {
2763
- // ignore stringify failures
2764
- }
2765
- return undefined;
2766
- }
2767
- extractClientModelId(metadata, originalRequest) {
2768
- const candidates = [
2769
- metadata.clientModelId,
2770
- metadata.originalModelId,
2771
- (metadata.target && typeof metadata.target === 'object'
2772
- ? metadata.target.clientModelId
2773
- : undefined),
2774
- originalRequest && typeof originalRequest === 'object'
2775
- ? originalRequest.model
2776
- : undefined,
2777
- originalRequest && typeof originalRequest === 'object'
2778
- ? originalRequest.originalModelId
2779
- : undefined
2780
- ];
2781
- for (const candidate of candidates) {
2782
- if (typeof candidate === 'string' && candidate.trim()) {
2783
- return candidate.trim();
2784
- }
2785
- }
2786
- return undefined;
2787
- }
2788
- cloneRequestPayload(payload) {
2789
- if (!payload || typeof payload !== 'object') {
2790
- return undefined;
2791
- }
2792
- try {
2793
- return JSON.parse(JSON.stringify(payload));
2794
- }
2795
- catch {
2796
- return undefined;
280
+ const legacyRunHubPipeline = this.runHubPipeline;
281
+ if (Object.prototype.hasOwnProperty.call(this, 'runHubPipeline') &&
282
+ typeof legacyRunHubPipeline === 'function') {
283
+ return await executePipelineViaLegacyOverride(this, input, legacyRunHubPipeline);
2797
284
  }
285
+ return await this.requestExecutor.execute(input);
2798
286
  }
2799
287
  async initializeRouteErrorHub() {
2800
- try {
2801
- this.routeErrorHub = initializeRouteErrorHub({ errorHandlingCenter: this.errorHandling });
2802
- await this.routeErrorHub.initialize();
2803
- }
2804
- catch (error) {
2805
- console.error('[RouteCodexHttpServer] Failed to initialize RouteErrorHub', error);
2806
- }
288
+ await initializeRouteErrorHub(this);
2807
289
  }
2808
290
  }
2809
291
  function createNoopPipelineLogger() {
@@ -2838,9 +320,8 @@ function createNoopPipelineLogger() {
2838
320
  getProviderLogs: () => emptyList(),
2839
321
  getStatistics: emptyStats,
2840
322
  clearLogs: noop,
2841
- exportLogs: () => ([]),
323
+ exportLogs: () => [],
2842
324
  log: noop
2843
325
  };
2844
326
  }
2845
- import { formatErrorForErrorCenter } from '../../../utils/error-center-payload.js';
2846
327
  //# sourceMappingURL=index.js.map