@jsonstudio/rcc 0.89.1205 → 0.89.1457

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 (391) hide show
  1. package/README.md +53 -1412
  2. package/configsamples/config.json +426 -0
  3. package/configsamples/config.reference.json +58 -0
  4. package/configsamples/provider/crs/config.v1.json +46 -0
  5. package/configsamples/provider/glm/config.v1.json +81 -0
  6. package/configsamples/provider/glm-anthropic/config.v1.json +45 -0
  7. package/configsamples/provider/iflow/config.v1.json +74 -0
  8. package/configsamples/provider/kimi/config.v1.json +41 -0
  9. package/configsamples/provider/lmstudio/config.v1.json +101 -0
  10. package/configsamples/provider/mimo/config.v1.json +35 -0
  11. package/configsamples/provider/modelscope/config.v1.json +96 -0
  12. package/configsamples/provider/qwen/config.v1.json +38 -0
  13. package/configsamples/provider/tab/config.v1.json +50 -0
  14. package/configsamples/provider/tabglm/config.v1.json +49 -0
  15. package/dist/build-info.js +2 -2
  16. package/dist/cli/commands/code.js +12 -6
  17. package/dist/cli/commands/code.js.map +1 -1
  18. package/dist/cli/commands/config.d.ts +2 -1
  19. package/dist/cli/commands/config.js +77 -103
  20. package/dist/cli/commands/config.js.map +1 -1
  21. package/dist/cli/commands/examples.js +6 -6
  22. package/dist/cli/commands/examples.js.map +1 -1
  23. package/dist/cli/commands/init.d.ts +28 -0
  24. package/dist/cli/commands/init.js +94 -0
  25. package/dist/cli/commands/init.js.map +1 -0
  26. package/dist/cli/commands/port.js +10 -2
  27. package/dist/cli/commands/port.js.map +1 -1
  28. package/dist/cli/commands/restart.js +5 -2
  29. package/dist/cli/commands/restart.js.map +1 -1
  30. package/dist/cli/commands/start.js +25 -22
  31. package/dist/cli/commands/start.js.map +1 -1
  32. package/dist/cli/commands/status.js +1 -0
  33. package/dist/cli/commands/status.js.map +1 -1
  34. package/dist/cli/commands/stop.js +1 -0
  35. package/dist/cli/commands/stop.js.map +1 -1
  36. package/dist/cli/config/bundled-docs.d.ts +20 -0
  37. package/dist/cli/config/bundled-docs.js +91 -0
  38. package/dist/cli/config/bundled-docs.js.map +1 -0
  39. package/dist/cli/config/init-config.d.ts +37 -0
  40. package/dist/cli/config/init-config.js +212 -0
  41. package/dist/cli/config/init-config.js.map +1 -0
  42. package/dist/cli/config/init-provider-catalog.d.ts +8 -0
  43. package/dist/cli/config/init-provider-catalog.js +187 -0
  44. package/dist/cli/config/init-provider-catalog.js.map +1 -0
  45. package/dist/cli/register/init-command.d.ts +3 -0
  46. package/dist/cli/register/init-command.js +5 -0
  47. package/dist/cli/register/init-command.js.map +1 -0
  48. package/dist/cli.js +28 -3
  49. package/dist/cli.js.map +1 -1
  50. package/dist/client/gemini/gemini-protocol-client.js +2 -1
  51. package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
  52. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +40 -16
  53. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  54. package/dist/client/openai/chat-protocol-client.js +2 -1
  55. package/dist/client/openai/chat-protocol-client.js.map +1 -1
  56. package/dist/client/responses/responses-protocol-client.js +2 -1
  57. package/dist/client/responses/responses-protocol-client.js.map +1 -1
  58. package/dist/config/risk-control-config.d.ts +94 -0
  59. package/dist/config/risk-control-config.js +196 -0
  60. package/dist/config/risk-control-config.js.map +1 -0
  61. package/dist/constants/index.d.ts +6 -0
  62. package/dist/constants/index.js +13 -0
  63. package/dist/constants/index.js.map +1 -1
  64. package/dist/docs/daemon-admin-ui.html +2113 -190
  65. package/dist/error-handling/quiet-error-handling-center.js +46 -8
  66. package/dist/error-handling/quiet-error-handling-center.js.map +1 -1
  67. package/dist/index.js +0 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/manager/modules/health/index.d.ts +1 -1
  70. package/dist/manager/modules/quota/antigravity-quota-manager.d.ts +70 -0
  71. package/dist/manager/modules/quota/antigravity-quota-manager.js +442 -0
  72. package/dist/manager/modules/quota/antigravity-quota-manager.js.map +1 -0
  73. package/dist/manager/modules/quota/index.d.ts +3 -127
  74. package/dist/manager/modules/quota/index.js +2 -1093
  75. package/dist/manager/modules/quota/index.js.map +1 -1
  76. package/dist/manager/modules/quota/provider-key-normalization.d.ts +3 -0
  77. package/dist/manager/modules/quota/provider-key-normalization.js +155 -0
  78. package/dist/manager/modules/quota/provider-key-normalization.js.map +1 -0
  79. package/dist/manager/modules/quota/provider-quota-daemon.cooldown.d.ts +9 -0
  80. package/dist/manager/modules/quota/provider-quota-daemon.cooldown.js +115 -0
  81. package/dist/manager/modules/quota/provider-quota-daemon.cooldown.js.map +1 -0
  82. package/dist/manager/modules/quota/provider-quota-daemon.d.ts +77 -0
  83. package/dist/manager/modules/quota/provider-quota-daemon.events.d.ts +12 -0
  84. package/dist/manager/modules/quota/provider-quota-daemon.events.js +239 -0
  85. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -0
  86. package/dist/manager/modules/quota/provider-quota-daemon.js +404 -0
  87. package/dist/manager/modules/quota/provider-quota-daemon.js.map +1 -0
  88. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.d.ts +11 -0
  89. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +192 -0
  90. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -0
  91. package/dist/manager/modules/quota/provider-quota-daemon.snapshot.d.ts +8 -0
  92. package/dist/manager/modules/quota/provider-quota-daemon.snapshot.js +96 -0
  93. package/dist/manager/modules/quota/provider-quota-daemon.snapshot.js.map +1 -0
  94. package/dist/manager/modules/quota/provider-quota-daemon.view.d.ts +19 -0
  95. package/dist/manager/modules/quota/provider-quota-daemon.view.js +37 -0
  96. package/dist/manager/modules/quota/provider-quota-daemon.view.js.map +1 -0
  97. package/dist/manager/modules/routing/index.d.ts +1 -0
  98. package/dist/manager/modules/routing/index.js +11 -25
  99. package/dist/manager/modules/routing/index.js.map +1 -1
  100. package/dist/manager/quota/provider-quota-center.d.ts +2 -0
  101. package/dist/manager/quota/provider-quota-center.js +80 -82
  102. package/dist/manager/quota/provider-quota-center.js.map +1 -1
  103. package/dist/modules/llmswitch/bridge.d.ts +16 -18
  104. package/dist/modules/llmswitch/bridge.js +293 -94
  105. package/dist/modules/llmswitch/bridge.js.map +1 -1
  106. package/dist/modules/llmswitch/core-loader.d.ts +4 -2
  107. package/dist/modules/llmswitch/core-loader.js +32 -20
  108. package/dist/modules/llmswitch/core-loader.js.map +1 -1
  109. package/dist/modules/pipeline/utils/colored-logger.js +3 -2
  110. package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
  111. package/dist/modules/pipeline/utils/debug-logger.js +1 -1
  112. package/dist/modules/pipeline/utils/debug-logger.js.map +1 -1
  113. package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -1
  114. package/dist/providers/auth/antigravity-userinfo-helper.js +25 -4
  115. package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
  116. package/dist/providers/auth/iflow-cookie-auth.js +0 -2
  117. package/dist/providers/auth/iflow-cookie-auth.js.map +1 -1
  118. package/dist/providers/auth/oauth-lifecycle.js +2 -23
  119. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  120. package/dist/providers/auth/tokenfile-auth.d.ts +2 -0
  121. package/dist/providers/auth/tokenfile-auth.js +33 -1
  122. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  123. package/dist/providers/core/config/camoufox-launcher.d.ts +5 -0
  124. package/dist/providers/core/config/camoufox-launcher.js +40 -4
  125. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  126. package/dist/providers/core/config/service-profiles.js +7 -18
  127. package/dist/providers/core/config/service-profiles.js.map +1 -1
  128. package/dist/providers/core/runtime/antigravity-quota-client.js +6 -3
  129. package/dist/providers/core/runtime/antigravity-quota-client.js.map +1 -1
  130. package/dist/providers/core/runtime/base-provider.d.ts +2 -7
  131. package/dist/providers/core/runtime/base-provider.js +84 -165
  132. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  133. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +7 -0
  134. package/dist/providers/core/runtime/gemini-cli-http-provider.js +368 -97
  135. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  136. package/dist/providers/core/runtime/http-request-executor.d.ts +3 -0
  137. package/dist/providers/core/runtime/http-request-executor.js +110 -38
  138. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  139. package/dist/providers/core/runtime/http-transport-provider.d.ts +17 -0
  140. package/dist/providers/core/runtime/http-transport-provider.js +165 -16
  141. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  142. package/dist/providers/core/runtime/provider-error-classifier.js +10 -0
  143. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  144. package/dist/providers/core/runtime/provider-factory.js +7 -5
  145. package/dist/providers/core/runtime/provider-factory.js.map +1 -1
  146. package/dist/providers/core/runtime/provider-runtime-metadata.d.ts +6 -0
  147. package/dist/providers/core/runtime/provider-runtime-metadata.js.map +1 -1
  148. package/dist/providers/core/runtime/rate-limit-manager.d.ts +1 -12
  149. package/dist/providers/core/runtime/rate-limit-manager.js +4 -77
  150. package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
  151. package/dist/providers/core/runtime/responses-provider.d.ts +1 -7
  152. package/dist/providers/core/runtime/responses-provider.js +12 -93
  153. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  154. package/dist/providers/core/strategies/oauth-auth-code-flow.js +12 -8
  155. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  156. package/dist/providers/core/utils/http-client.js +36 -46
  157. package/dist/providers/core/utils/http-client.js.map +1 -1
  158. package/dist/providers/core/utils/provider-error-logger.d.ts +1 -1
  159. package/dist/providers/core/utils/provider-error-reporter.d.ts +3 -1
  160. package/dist/providers/core/utils/provider-error-reporter.js +3 -0
  161. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  162. package/dist/providers/core/utils/snapshot-writer.js +1 -4
  163. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  164. package/dist/providers/mock/mock-provider-runtime.js +57 -27
  165. package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
  166. package/dist/scripts/camoufox/launch-auth.mjs +193 -58
  167. package/dist/server/handlers/handler-utils.js +8 -3
  168. package/dist/server/handlers/handler-utils.js.map +1 -1
  169. package/dist/server/handlers/responses-handler.js +1 -1
  170. package/dist/server/handlers/responses-handler.js.map +1 -1
  171. package/dist/server/runtime/http-server/daemon-admin/auth-handler.d.ts +2 -0
  172. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +103 -0
  173. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -0
  174. package/dist/server/runtime/http-server/daemon-admin/auth-session.d.ts +5 -0
  175. package/dist/server/runtime/http-server/daemon-admin/auth-session.js +77 -0
  176. package/dist/server/runtime/http-server/daemon-admin/auth-session.js.map +1 -0
  177. package/dist/server/runtime/http-server/daemon-admin/auth-store.d.ts +18 -0
  178. package/dist/server/runtime/http-server/daemon-admin/auth-store.js +89 -0
  179. package/dist/server/runtime/http-server/daemon-admin/auth-store.js.map +1 -0
  180. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +1 -2
  181. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  182. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +226 -24
  183. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  184. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +47 -8
  185. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
  186. package/dist/server/runtime/http-server/daemon-admin/restart-handler.js +1 -1
  187. package/dist/server/runtime/http-server/daemon-admin/restart-handler.js.map +1 -1
  188. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +1 -1
  189. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -1
  190. package/dist/server/runtime/http-server/daemon-admin/status-handler.js +68 -4
  191. package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -1
  192. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +3 -4
  193. package/dist/server/runtime/http-server/daemon-admin-routes.js +9 -14
  194. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  195. package/dist/server/runtime/http-server/executor-metadata.js +1 -1
  196. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  197. package/dist/server/runtime/http-server/executor-response.js +0 -16
  198. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  199. package/dist/server/runtime/http-server/hub-shadow-compare.js +110 -34
  200. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
  201. package/dist/server/runtime/http-server/index.d.ts +5 -3
  202. package/dist/server/runtime/http-server/index.js +281 -136
  203. package/dist/server/runtime/http-server/index.js.map +1 -1
  204. package/dist/server/runtime/http-server/middleware.js +19 -1
  205. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  206. package/dist/server/runtime/http-server/request-executor.js +59 -24
  207. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  208. package/dist/server/runtime/http-server/routes.js +12 -3
  209. package/dist/server/runtime/http-server/routes.js.map +1 -1
  210. package/dist/server/runtime/http-server/session-dir.d.ts +2 -0
  211. package/dist/server/runtime/http-server/session-dir.js +59 -0
  212. package/dist/server/runtime/http-server/session-dir.js.map +1 -0
  213. package/dist/server/runtime/http-server/types.d.ts +0 -4
  214. package/dist/server/utils/utf8-chunk-buffer.js +6 -3
  215. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -1
  216. package/dist/server/utils/warmup-storm-tracker.js +1 -1
  217. package/dist/server/utils/warmup-storm-tracker.js.map +1 -1
  218. package/dist/server-factory.d.ts +6 -28
  219. package/dist/server-factory.js +8 -93
  220. package/dist/server-factory.js.map +1 -1
  221. package/dist/token-daemon/index.js +2 -2
  222. package/dist/token-daemon/index.js.map +1 -1
  223. package/dist/token-daemon/provider-registry.js +0 -1
  224. package/dist/token-daemon/provider-registry.js.map +1 -1
  225. package/dist/token-daemon/server-utils.js +8 -9
  226. package/dist/token-daemon/server-utils.js.map +1 -1
  227. package/dist/token-daemon/token-utils.js +1 -1
  228. package/dist/token-daemon/token-utils.js.map +1 -1
  229. package/dist/tools/semantic-replay.js +2 -2
  230. package/dist/tools/semantic-replay.js.map +1 -1
  231. package/dist/tools/stats-request-events.d.ts +1 -1
  232. package/dist/tools/stats-usage.js +6 -3
  233. package/dist/tools/stats-usage.js.map +1 -1
  234. package/dist/utils/llms-engine-shadow.d.ts +19 -0
  235. package/dist/utils/llms-engine-shadow.js +209 -0
  236. package/dist/utils/llms-engine-shadow.js.map +1 -0
  237. package/dist/utils/runtime-versions.js +2 -1
  238. package/dist/utils/runtime-versions.js.map +1 -1
  239. package/dist/utils/strip-internal-keys.d.ts +12 -0
  240. package/dist/utils/strip-internal-keys.js +28 -0
  241. package/dist/utils/strip-internal-keys.js.map +1 -0
  242. package/docs/ARCHITECTURE.md +402 -0
  243. package/docs/CHAT_PROCESS_PROTOCOL_AND_PIPELINE.md +221 -0
  244. package/docs/CODEX_AND_CLAUDE_CODE.md +69 -0
  245. package/docs/CONFIG_ARCHITECTURE.md +517 -0
  246. package/docs/ERROR_HANDLING_AUDIT.md +0 -0
  247. package/docs/GCLI2API_PARITY_GAPS.md +98 -0
  248. package/docs/INSTALLATION_AND_QUICKSTART.md +74 -0
  249. package/docs/INSTRUCTION_MARKUP.md +89 -0
  250. package/docs/MODULE_ENHANCEMENT_SYSTEM.md +666 -0
  251. package/docs/PORTS.md +36 -0
  252. package/docs/PROVIDERS_BUILTIN.md +111 -0
  253. package/docs/PROVIDER_TYPES.md +55 -0
  254. package/docs/SERVERTOOL_CLOCK_DESIGN.md +233 -0
  255. package/docs/USAGE_HANDLING_ANALYSIS.md +335 -0
  256. package/docs/USER_CONFIG_PARSER_CHANGES.md +175 -0
  257. package/docs/V3_INBOUND_OUTBOUND_DESIGN.md +86 -0
  258. package/docs/VIRTUAL_ROUTER_PRIORITY_AND_HEALTH.md +125 -0
  259. package/docs/anthropic-request-golden-samples.md +50 -0
  260. package/docs/antigravity-gemini-format-cleanup.md +102 -0
  261. package/docs/antigravity-routing-contract.md +31 -0
  262. package/docs/ccr-alignment-enhancetool.md +105 -0
  263. package/docs/chat-glm-500-analysis.md +79 -0
  264. package/docs/chat-request-golden-samples.md +42 -0
  265. package/docs/chat-semantic-expansion-plan.md +84 -0
  266. package/docs/cli-command-inventory.md +76 -0
  267. package/docs/codex-samples-replay.md +50 -0
  268. package/docs/daemon-admin-api-design.md +350 -0
  269. package/docs/daemon-admin-module-structure.md +169 -0
  270. package/docs/daemon-admin-ui.html +3394 -0
  271. package/docs/debug-system-design.md +734 -0
  272. package/docs/debugging/gemini-sse-root-cause.md +52 -0
  273. package/docs/debugging/sse_encoding_failure_analysis.md +53 -0
  274. package/docs/dry-run/README.md +721 -0
  275. package/docs/error-handling-v2.md +92 -0
  276. package/docs/exec-command-guard-policy.example.v1.json +42 -0
  277. package/docs/fixes/gemini-protocol-mapping.md +57 -0
  278. package/docs/fixes/oauth-portal-timing-fix.md +202 -0
  279. package/docs/fixes/web-search-hop3-fix.md +265 -0
  280. package/docs/glm-api-reference.md +390 -0
  281. package/docs/glm-chat-completions.md +1779 -0
  282. package/docs/glm-history-inline-images.md +44 -0
  283. package/docs/golden-ci-library.md +66 -0
  284. package/docs/lmstudio-dry-run-summary.md +203 -0
  285. package/docs/lmstudio-tool-calling.md +214 -0
  286. package/docs/mapping-tables/anthropic-to-openai.json +290 -0
  287. package/docs/mapping-tables/iflow-to-openai.json +215 -0
  288. package/docs/mapping-tables/openai-passthrough.json +190 -0
  289. package/docs/mapping-tables/openai-to-iflow.json +227 -0
  290. package/docs/monitoring/Design.md +61 -0
  291. package/docs/multi-token-auth-guide.md +66 -0
  292. package/docs/oauth-authentication-guide.md +168 -0
  293. package/docs/oauth-iflow-implementation.md +153 -0
  294. package/docs/pipeline-routing-report.md +209 -0
  295. package/docs/plans/manager-daemon/PLAN.md +86 -0
  296. package/docs/plans/provider-config-v2-plan.md +176 -0
  297. package/docs/plans/provider-runtime-manager-plan.md +209 -0
  298. package/docs/plans/transparent-429-failover.md +89 -0
  299. package/docs/plans/unified-hub-framework-v1.md +245 -0
  300. package/docs/provider-config-v2-ui-design.md +181 -0
  301. package/docs/provider-quota-design.md +129 -0
  302. package/docs/providers/gemini-provider.md +62 -0
  303. package/docs/providers/lmstudio-v2-migration-report.md +102 -0
  304. package/docs/providers/provider-composite-design.md +142 -0
  305. package/docs/providers/provider-composite-testing.md +98 -0
  306. package/docs/providers/provider-type-only-migration.md +111 -0
  307. package/docs/rccx-wasm-migration.md +74 -0
  308. package/docs/refactoring/architecture-comparison-diagram.md +140 -0
  309. package/docs/refactoring/compatibility-v2-architecture-design.md +738 -0
  310. package/docs/refactoring/workflow-compatibility-refactoring-design.md +361 -0
  311. package/docs/reports/routing-classification-report.json +24 -0
  312. package/docs/reports/routing-classification-report.md +18 -0
  313. package/docs/reports/thinking-keywords-report.json +19 -0
  314. package/docs/responses/README.md +156 -0
  315. package/docs/responses-generic-provider.md +86 -0
  316. package/docs/responses-passthrough-provider-design.md +202 -0
  317. package/docs/routing-awrr-health-weighted-round-robin.md +179 -0
  318. package/docs/routing-instructions.md +393 -0
  319. package/docs/servertool-framework.md +65 -0
  320. package/docs/stop-message-auto.md +225 -0
  321. package/docs/streaming-flow.html +30 -0
  322. package/docs/streaming-flow.md +182 -0
  323. package/docs/token-daemon-preview.html +490 -0
  324. package/docs/token-refresh-daemon-plan.md +269 -0
  325. package/docs/transformation-tables/Gemini-FinishReason/345/256/214/346/225/264/350/275/254/346/215/242/350/241/250.json +233 -0
  326. package/docs/transformation-tables/README.md +225 -0
  327. package/docs/transformation-tables/claude-code-router-anthropic-to-gemini.json +283 -0
  328. package/docs/transformation-tables/claude-code-router-anthropic-to-openai.json +208 -0
  329. package/docs/transformation-tables/claude-code-router-openai-to-anthropic.json +261 -0
  330. package/docs/transformation-tables/claude-code-router-openai-to-gemini.json +208 -0
  331. package/docs/transformation-tables/claude-code-router-openai-to-lmstudio.json +182 -0
  332. package/docs/transformation-tables/claude-code-router-openai-to-ollama.json +250 -0
  333. package/docs/transformation-tables/claude-code-router-openai-to-textgenwebui.json +295 -0
  334. package/docs/transformation-tables/claude-code-router-provider-conversions.json +193 -0
  335. package/docs/transformation-tables//345/256/214/346/225/264/347/232/204/345/267/245/345/205/267/346/211/247/350/241/214/346/265/201/347/250/213/350/275/254/346/215/242/350/241/250.json +299 -0
  336. package/docs/transformation-tables//345/257/271/350/257/235/345/216/206/345/217/262/347/273/264/346/212/244/345/210/206/346/236/220.md +134 -0
  337. package/docs/transformation-tables//345/267/245/345/205/267/350/260/203/347/224/250/346/250/241/345/274/217/345/210/206/346/236/220.md +158 -0
  338. package/docs/transformation-tables//347/212/266/346/200/201/347/256/241/347/220/206/351/234/200/346/261/202/345/210/206/346/236/220.md +175 -0
  339. package/docs/transformation-tables//351/235/231/346/200/201/350/241/250vs/345/212/250/346/200/201/345/210/206/346/236/220.md +189 -0
  340. package/docs/transformation-tables//351/235/231/346/200/201/350/241/250/345/207/206/347/241/256/346/200/247/350/257/204/344/274/260.md +179 -0
  341. package/docs/transformation-tables//351/235/236/346/265/201/345/274/217/345/234/272/346/231/257/345/210/206/346/236/220.md +189 -0
  342. package/docs/v2-architecture/IMPLEMENTATION-ROADMAP.md +367 -0
  343. package/docs/v2-architecture/OPTIMIZED-DESIGN.md +827 -0
  344. package/docs/v2-architecture/PRERUN-CONNECTION-DESIGN.md +716 -0
  345. package/docs/v2-architecture/README.md +549 -0
  346. package/docs/verification/modelscope-verify.md +59 -0
  347. package/docs/verified-configs/README.md +60 -0
  348. package/docs/verified-configs/v0.45.0/README.md +244 -0
  349. package/docs/verified-configs/v0.45.0/lmstudio-5521-gpt-oss-20b-mlx.json +135 -0
  350. package/docs/verified-configs/v0.45.0/merged-config.5521.json +1205 -0
  351. package/docs/verified-configs/v0.45.0/merged-config.qwen-5522.json +1559 -0
  352. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-final.json +221 -0
  353. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-fixed.json +242 -0
  354. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus.json +242 -0
  355. package/docs/web-search-service-design.md +322 -0
  356. package/package.json +26 -15
  357. package/scripts/build-core.mjs +3 -1
  358. package/scripts/camoufox/launch-auth.mjs +193 -58
  359. package/scripts/ci/repo-sanity.mjs +138 -0
  360. package/scripts/mock-provider/run-regressions.mjs +157 -1
  361. package/scripts/monitor-diff.mjs +126 -0
  362. package/scripts/pack-mode.mjs +19 -1
  363. package/scripts/pack-rcc.mjs +63 -0
  364. package/scripts/run-bg.sh +0 -14
  365. package/scripts/tests/ci-jest.mjs +119 -0
  366. package/scripts/tools-dev/responses-debug-client/README.md +23 -0
  367. package/scripts/tools-dev/responses-debug-client/payloads/poem.json +13 -0
  368. package/scripts/tools-dev/responses-debug-client/payloads/sample-no-tools.json +98 -0
  369. package/scripts/tools-dev/responses-debug-client/payloads/text.json +13 -0
  370. package/scripts/tools-dev/responses-debug-client/payloads/tool.json +27 -0
  371. package/scripts/tools-dev/responses-debug-client/run.mjs +65 -0
  372. package/scripts/tools-dev/responses-debug-client/src/index.ts +281 -0
  373. package/scripts/tools-dev/run-llmswitch-chat.mjs +53 -0
  374. package/scripts/tools-dev/server-tools-dev/run-web-fetch.mjs +65 -0
  375. package/scripts/unified-hub-shadow-compare.mjs +33 -13
  376. package/scripts/vendor-core.mjs +13 -3
  377. package/scripts/verify-e2e-toolcall.mjs +115 -26
  378. package/dist/modules/llmswitch/pipeline-registry.d.ts +0 -57
  379. package/dist/modules/llmswitch/pipeline-registry.js +0 -229
  380. package/dist/modules/llmswitch/pipeline-registry.js.map +0 -1
  381. package/dist/server/RouteCodexServer.d.ts +0 -13
  382. package/dist/server/RouteCodexServer.js +0 -25
  383. package/dist/server/RouteCodexServer.js.map +0 -1
  384. package/dist/v2/conversion/hub/snapshot-recorder.d.ts +0 -12
  385. package/dist/v2/conversion/hub/snapshot-recorder.js +0 -22
  386. package/dist/v2/conversion/hub/snapshot-recorder.js.map +0 -1
  387. package/scripts/test-fc-responses.mjs +0 -66
  388. package/scripts/test-guidance.mjs +0 -100
  389. package/scripts/test-iflow-web-search.mjs +0 -141
  390. package/scripts/test-iflow.mjs +0 -379
  391. package/scripts/test-tool-exec.mjs +0 -26
@@ -7,6 +7,12 @@ import fs from 'node:fs';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
9
9
 
10
+ function isTruthy(value) {
11
+ if (!value) return false;
12
+ const v = String(value).trim().toLowerCase();
13
+ return v === '1' || v === 'true' || v === 'yes' || v === 'on';
14
+ }
15
+
10
16
  function parseArgs(argv) {
11
17
  const args = { profile: 'default', url: '', autoMode: '', devMode: false };
12
18
  const list = argv.slice(2);
@@ -65,6 +71,44 @@ async function getCamoufoxCacheRoot() {
65
71
  });
66
72
  }
67
73
 
74
+ function resolveCamoufoxBinary(cacheRoot) {
75
+ const override = (process.env.ROUTECODEX_CAMOUFOX_BINARY || '').trim();
76
+ if (override) {
77
+ return override;
78
+ }
79
+
80
+ const isMac = process.platform === 'darwin';
81
+ if (!cacheRoot && isMac) {
82
+ // Best-effort fallback when python3 is unavailable/broken:
83
+ // Camoufox's packaged app is commonly placed under ~/Library/Caches/camoufox/Camoufox.app.
84
+ const guessed = path.join(os.homedir(), 'Library', 'Caches', 'camoufox');
85
+ if (fs.existsSync(path.join(guessed, 'Camoufox.app'))) {
86
+ cacheRoot = guessed;
87
+ }
88
+ }
89
+ if (cacheRoot && isMac) {
90
+ const appPath = path.join(cacheRoot, 'Camoufox.app');
91
+ const macBinary = path.join(appPath, 'Contents', 'MacOS', 'camoufox');
92
+ if (fs.existsSync(macBinary)) {
93
+ return macBinary;
94
+ }
95
+ }
96
+
97
+ try {
98
+ const located = spawnSync('which', ['camoufox'], { encoding: 'utf-8' });
99
+ if (located.status === 0) {
100
+ const resolved = String(located.stdout || '').trim();
101
+ if (resolved) {
102
+ return resolved;
103
+ }
104
+ }
105
+ } catch {
106
+ // ignore lookup failure
107
+ }
108
+
109
+ return 'camoufox';
110
+ }
111
+
68
112
  async function ensureProfileDir(profileId) {
69
113
  const root = path.join(os.homedir(), '.routecodex', 'camoufox-profiles');
70
114
  const dir = path.join(root, profileId);
@@ -108,17 +152,16 @@ async function main() {
108
152
 
109
153
  const cacheRoot = await getCamoufoxCacheRoot();
110
154
  if (!cacheRoot) {
111
- console.error(
112
- '[camoufox-launch-auth] Failed to resolve Camoufox cache root via "python3 -m camoufox path"'
155
+ console.warn(
156
+ '[camoufox-launch-auth] Failed to resolve Camoufox cache root via "python3 -m camoufox path"; falling back to PATH/override.'
113
157
  );
114
- process.exit(1);
115
158
  }
116
159
 
117
160
  const camoufoxBinary = resolveCamoufoxBinary(cacheRoot);
118
161
 
119
162
  if (autoMode && autoMode.trim().toLowerCase() === 'iflow') {
120
163
  try {
121
- await runIflowAutoFlow({ url, profileDir, profileId, camoufoxBinary, devMode });
164
+ await runAutoFlowWithFallback('iflow', { url, profileDir, profileId, camoufoxBinary, devMode });
122
165
  process.exit(0);
123
166
  } catch (error) {
124
167
  console.error(
@@ -132,7 +175,7 @@ async function main() {
132
175
 
133
176
  if (autoMode && autoMode.trim().toLowerCase() === 'gemini') {
134
177
  try {
135
- await runGeminiAutoFlow({ url, profileDir, camoufoxBinary, devMode });
178
+ await runAutoFlowWithFallback('gemini', { url, profileDir, profileId, camoufoxBinary, devMode });
136
179
  process.exit(0);
137
180
  } catch (error) {
138
181
  console.error(
@@ -146,7 +189,7 @@ async function main() {
146
189
 
147
190
  if (autoMode && autoMode.trim().toLowerCase() === 'antigravity') {
148
191
  try {
149
- await runAntigravityAutoFlow({ url, profileDir, camoufoxBinary, devMode });
192
+ await runAutoFlowWithFallback('antigravity', { url, profileDir, profileId, camoufoxBinary, devMode });
150
193
  process.exit(0);
151
194
  } catch (error) {
152
195
  console.error(
@@ -166,59 +209,29 @@ main().catch((err) => {
166
209
  process.exit(1);
167
210
  });
168
211
 
169
- function resolveCamoufoxBinary(cacheRoot) {
170
- const isMac = process.platform === 'darwin';
171
- if (isMac) {
172
- const appPath = path.join(cacheRoot, 'Camoufox.app');
173
- const macBinary = path.join(appPath, 'Contents', 'MacOS', 'camoufox');
174
- if (fs.existsSync(macBinary)) {
175
- return macBinary;
176
- }
177
- }
178
- try {
179
- const located = spawnSync('which', ['camoufox'], { encoding: 'utf-8' });
180
- if (located.status === 0) {
181
- const resolved = located.stdout.trim();
182
- if (resolved) {
183
- return resolved;
212
+ async function launchManualCamoufox({ camoufoxBinary, profileDir, url }) {
213
+ let browserExitCode = 0;
214
+ let browser = null;
215
+ const shutdownBrowser = (signal = 'SIGTERM') => {
216
+ try {
217
+ if (!browser) {
218
+ return;
184
219
  }
220
+ browser.kill(signal);
221
+ } catch {
222
+ // ignore
185
223
  }
186
- } catch {
187
- // ignore lookup failure
188
- }
189
- return 'camoufox';
190
- }
224
+ };
225
+ ['SIGTERM', 'SIGINT', 'SIGHUP'].forEach((signal) => {
226
+ process.on(signal, () => shutdownBrowser(signal));
227
+ });
191
228
 
192
- async function launchManualCamoufox({ camoufoxBinary, profileDir, url }) {
193
- let browserExitCode = 0;
194
229
  try {
195
- const browser = spawn(camoufoxBinary, ['-profile', profileDir, url], {
196
- detached: true,
230
+ browser = spawn(camoufoxBinary, ['-profile', profileDir, url], {
231
+ detached: false,
197
232
  stdio: 'ignore'
198
233
  });
199
234
 
200
- const shutdownBrowser = (signal = 'SIGTERM') => {
201
- try {
202
- if (browser.pid) {
203
- try {
204
- process.kill(-browser.pid, signal);
205
- return;
206
- } catch {
207
- // process group kill may fail on non-unix systems; fall back to direct kill
208
- }
209
- }
210
- browser.kill(signal);
211
- } catch {
212
- // ignore
213
- }
214
- };
215
-
216
- ['SIGTERM', 'SIGINT'].forEach((signal) => {
217
- process.on(signal, () => {
218
- shutdownBrowser(signal);
219
- });
220
- });
221
-
222
235
  browserExitCode = await new Promise((resolve) => {
223
236
  browser.on('exit', (code) => resolve(code ?? 0));
224
237
  browser.on('error', () => resolve(1));
@@ -234,6 +247,95 @@ async function launchManualCamoufox({ camoufoxBinary, profileDir, url }) {
234
247
  process.exit(browserExitCode);
235
248
  }
236
249
 
250
+ function isSelectorOrTimeoutError(error) {
251
+ const message = error instanceof Error ? error.message : String(error || '');
252
+ return (
253
+ /timeout/i.test(message) ||
254
+ /waiting for selector/i.test(message) ||
255
+ /strict mode violation/i.test(message) ||
256
+ message.includes('未能定位') ||
257
+ message.includes('无法定位')
258
+ );
259
+ }
260
+
261
+ async function runHeadedManualAssistFlow({ url, profileDir, camoufoxBinary, timeoutMs, label }) {
262
+ let firefox;
263
+ try {
264
+ ({ firefox } = await import('playwright-core'));
265
+ } catch (error) {
266
+ throw new Error(
267
+ `playwright-core is required for headed manual assist (${error instanceof Error ? error.message : String(error)})`
268
+ );
269
+ }
270
+
271
+ console.warn(
272
+ `[camoufox-launch-auth] ${label}: falling back to headed mode for manual completion (no selector match).`
273
+ );
274
+ cleanupExistingCamoufox(profileDir);
275
+ const context = await firefox.launchPersistentContext(profileDir, {
276
+ executablePath: camoufoxBinary,
277
+ headless: false,
278
+ acceptDownloads: false
279
+ });
280
+
281
+ let closed = false;
282
+ const shutdown = async () => {
283
+ if (closed) return;
284
+ closed = true;
285
+ await context.close().catch(() => {});
286
+ };
287
+ ['SIGTERM', 'SIGINT', 'SIGHUP'].forEach((signal) => {
288
+ process.on(signal, () => {
289
+ void shutdown().finally(() => process.exit(0));
290
+ });
291
+ });
292
+
293
+ try {
294
+ const page = context.pages()[0] || (await context.newPage());
295
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {});
296
+ console.log('[camoufox-launch-auth] Headed browser opened. Please complete OAuth manually...');
297
+ const callbackPage = await waitForCallback(context, page, timeoutMs);
298
+ await callbackPage.waitForLoadState('load', { timeout: 120000 }).catch(() => {});
299
+ console.log('[camoufox-launch-auth] OAuth callback detected, manual completion finished.');
300
+ } finally {
301
+ await shutdown();
302
+ }
303
+ }
304
+
305
+ async function runAutoFlowWithFallback(kind, options) {
306
+ const mode = String(kind || '').trim().toLowerCase();
307
+ const label = mode || 'auto';
308
+ const timeoutMs = Number(process.env.ROUTECODEX_CAMOUFOX_GEMINI_TIMEOUT_MS || 120_000);
309
+
310
+ try {
311
+ if (mode === 'iflow') {
312
+ await runIflowAutoFlow(options);
313
+ return;
314
+ }
315
+ if (mode === 'gemini') {
316
+ await runGeminiAutoFlow(options);
317
+ return;
318
+ }
319
+ if (mode === 'antigravity') {
320
+ await runAntigravityAutoFlow(options);
321
+ return;
322
+ }
323
+ throw new Error(`Unknown auto mode: ${mode}`);
324
+ } catch (error) {
325
+ if (!options.devMode && isSelectorOrTimeoutError(error)) {
326
+ await runHeadedManualAssistFlow({
327
+ url: options.url,
328
+ profileDir: options.profileDir,
329
+ camoufoxBinary: options.camoufoxBinary,
330
+ timeoutMs: Number(process.env.ROUTECODEX_OAUTH_TIMEOUT_MS || 10 * 60_000),
331
+ label
332
+ });
333
+ return;
334
+ }
335
+ throw error;
336
+ }
337
+ }
338
+
237
339
  async function runIflowAutoFlow({ url, profileDir, profileId, camoufoxBinary, devMode }) {
238
340
  let firefox;
239
341
  try {
@@ -252,6 +354,17 @@ async function runIflowAutoFlow({ url, profileDir, profileId, camoufoxBinary, de
252
354
  headless,
253
355
  acceptDownloads: false
254
356
  });
357
+ let closing = false;
358
+ const shutdown = async () => {
359
+ if (closing) return;
360
+ closing = true;
361
+ await context.close().catch(() => {});
362
+ };
363
+ ['SIGTERM', 'SIGINT', 'SIGHUP'].forEach((signal) => {
364
+ process.on(signal, () => {
365
+ void shutdown().finally(() => process.exit(0));
366
+ });
367
+ });
255
368
 
256
369
  let callbackObserved = false;
257
370
  try {
@@ -352,7 +465,7 @@ async function runIflowAutoFlow({ url, profileDir, profileId, camoufoxBinary, de
352
465
  }
353
466
  throw error;
354
467
  } finally {
355
- await context.close().catch(() => {});
468
+ await shutdown();
356
469
  }
357
470
  }
358
471
 
@@ -374,6 +487,17 @@ async function runGeminiAutoFlow({ url, profileDir, camoufoxBinary, devMode }) {
374
487
  headless: !devMode,
375
488
  acceptDownloads: false
376
489
  });
490
+ let closing = false;
491
+ const shutdown = async () => {
492
+ if (closing) return;
493
+ closing = true;
494
+ await context.close().catch(() => {});
495
+ };
496
+ ['SIGTERM', 'SIGINT', 'SIGHUP'].forEach((signal) => {
497
+ process.on(signal, () => {
498
+ void shutdown().finally(() => process.exit(0));
499
+ });
500
+ });
377
501
 
378
502
  let callbackObserved = false;
379
503
  try {
@@ -440,7 +564,7 @@ async function runGeminiAutoFlow({ url, profileDir, camoufoxBinary, devMode }) {
440
564
  await callbackPage.waitForLoadState('load', { timeout: timeoutMs }).catch(() => {});
441
565
  console.log('[camoufox-launch-auth] OAuth callback detected, automation complete.');
442
566
  } finally {
443
- await context.close().catch(() => {});
567
+ await shutdown();
444
568
  }
445
569
  }
446
570
 
@@ -462,6 +586,17 @@ async function runAntigravityAutoFlow({ url, profileDir, camoufoxBinary, devMode
462
586
  headless: !devMode,
463
587
  acceptDownloads: false
464
588
  });
589
+ let closing = false;
590
+ const shutdown = async () => {
591
+ if (closing) return;
592
+ closing = true;
593
+ await context.close().catch(() => {});
594
+ };
595
+ ['SIGTERM', 'SIGINT', 'SIGHUP'].forEach((signal) => {
596
+ process.on(signal, () => {
597
+ void shutdown().finally(() => process.exit(0));
598
+ });
599
+ });
465
600
 
466
601
  let callbackObserved = false;
467
602
  try {
@@ -544,7 +679,7 @@ async function runAntigravityAutoFlow({ url, profileDir, camoufoxBinary, devMode
544
679
  }
545
680
  throw error;
546
681
  } finally {
547
- await context.close().catch(() => {});
682
+ await shutdown();
548
683
  }
549
684
  }
550
685
 
@@ -584,7 +719,7 @@ async function waitForElementInPages(context, selector, timeoutMs) {
584
719
  return null;
585
720
  }
586
721
 
587
- async function waitForCallback(context, fallbackPage) {
722
+ async function waitForCallback(context, fallbackPage, timeoutMs = 120000) {
588
723
  const isCallbackUrl = (current) => {
589
724
  if (typeof current !== 'string') {
590
725
  return false;
@@ -605,13 +740,13 @@ async function waitForCallback(context, fallbackPage) {
605
740
  }
606
741
 
607
742
  try {
608
- await fallbackPage.waitForURL((current) => isCallbackUrl(current), { timeout: 120000 });
743
+ await fallbackPage.waitForURL((current) => isCallbackUrl(current), { timeout: timeoutMs });
609
744
  return fallbackPage;
610
745
  } catch {
611
746
  // ignore and wait for popup
612
747
  }
613
748
 
614
- const callback = await context.waitForEvent('page', { timeout: 120000 });
749
+ const callback = await context.waitForEvent('page', { timeout: timeoutMs });
615
750
  await callback.waitForLoadState('domcontentloaded', { timeout: 60000 }).catch(() => {});
616
751
  return callback;
617
752
  }
@@ -0,0 +1,138 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ function runGit(args) {
6
+ const out = spawnSync('git', args, { encoding: 'utf8' });
7
+ if (out.status !== 0) {
8
+ throw new Error(`git ${args.join(' ')} failed: ${out.stderr || out.stdout}`);
9
+ }
10
+ return String(out.stdout || '');
11
+ }
12
+
13
+ function isIgnoredByGit(p) {
14
+ const out = spawnSync('git', ['check-ignore', '-q', p], { encoding: 'utf8' });
15
+ return out.status === 0;
16
+ }
17
+
18
+ function listRootEntries() {
19
+ const cwd = process.cwd();
20
+ return fs
21
+ .readdirSync(cwd, { withFileTypes: true })
22
+ .filter((d) => d.name !== '.git')
23
+ .filter((d) => !isIgnoredByGit(d.name))
24
+ .map((d) => d.name)
25
+ .sort();
26
+ }
27
+
28
+ function isForbiddenRootFile(p) {
29
+ const base = path.posix.basename(p);
30
+ const allow = new Set(['AGENTS.md', 'README.md', 'task.md']);
31
+ if (allow.has(base)) return false;
32
+ if (/^test-.*\.(mjs|js|ts|py)$/i.test(base)) return true;
33
+ if (/^debug-.*\.(mjs|js|ts)$/i.test(base)) return true;
34
+ if (/\.pid$/i.test(base)) return true;
35
+ if (/\.tgz$/i.test(base)) return true;
36
+ if (base === 'plan.md') return true;
37
+ if (base === 'task-fallback.md') return true;
38
+ if (base === 'task.archive.md') return true;
39
+ if (base === 'WARP.md') return true;
40
+ if (base === 'CLAUDE.md') return true;
41
+ if (/(_SUMMARY|_FIX)_/i.test(base) && base.toLowerCase().endsWith('.md')) return true;
42
+ // Disallow ad-hoc root markdown by default (docs belong under docs/).
43
+ if (base.toLowerCase().endsWith('.md')) return true;
44
+ return false;
45
+ }
46
+
47
+ function isForbiddenTrackedPath(p) {
48
+ // Keep the rules narrow and explicit: this is an audit guard, not a policy engine.
49
+ if (p.startsWith('docs/archive/')) return true;
50
+ if (p.startsWith('scripts/dev/')) return true;
51
+ if (p.startsWith('scripts/test-') && p.endsWith('.mjs')) return true;
52
+ if (p.startsWith('tools/')) return true;
53
+ if (p.startsWith('replay/')) return true;
54
+ if (p.startsWith('servertool/')) return true;
55
+ if (p.startsWith('verified-configs/')) return true;
56
+ if (p.startsWith('interpreter/')) return true;
57
+ if (p.startsWith('exporters/')) return true;
58
+ if (p.startsWith('bin/')) return true;
59
+ if (p.startsWith('.npm-cache-local/')) return true;
60
+ if (p.startsWith('.claude/')) return true;
61
+ if (p.startsWith('.iflow/')) return true;
62
+ if (p === '.secrets.baseline') return true;
63
+ if (p.endsWith('/.DS_Store') || p === '.DS_Store') return true;
64
+ return false;
65
+ }
66
+
67
+ function checkRootLayout() {
68
+ // Fixed top-level layout. Adding new root entries requires an explicit policy change.
69
+ const allowed = new Set([
70
+ 'eslint.config.js',
71
+ '.github',
72
+ '.gitignore',
73
+ 'AGENTS.md',
74
+ 'README.md',
75
+ 'config',
76
+ 'configsamples',
77
+ 'dist',
78
+ 'docs',
79
+ 'jest.config.js',
80
+ 'node_modules',
81
+ 'package',
82
+ 'package-lock.json',
83
+ 'package.json',
84
+ 'rcc',
85
+ 'samples',
86
+ 'scripts',
87
+ 'sharedmodule',
88
+ 'src',
89
+ 'task.md',
90
+ 'tests',
91
+ 'tmp',
92
+ 'tsconfig.json',
93
+ 'vendor',
94
+ ]);
95
+
96
+ const rootEntries = listRootEntries();
97
+ const unexpected = rootEntries.filter((name) => !allowed.has(name));
98
+ if (unexpected.length) {
99
+ console.error('[repo-sanity] unexpected root entries (top-level is fixed):');
100
+ for (const name of unexpected) console.error(`- ${name}`);
101
+ process.exit(2);
102
+ }
103
+ }
104
+
105
+ function checkUntrackedNotIgnored() {
106
+ // Fail fast if anything new appears outside gitignore (anywhere in repo).
107
+ const out = runGit(['ls-files', '--others', '--exclude-standard']);
108
+ const paths = out
109
+ .split('\n')
110
+ .map((s) => s.trim())
111
+ .filter(Boolean);
112
+ if (paths.length) {
113
+ console.error('[repo-sanity] untracked files not ignored (add them to git or gitignore):');
114
+ for (const p of paths.slice(0, 200)) console.error(`- ${p}`);
115
+ if (paths.length > 200) console.error(`- ... (${paths.length - 200} more)`);
116
+ process.exit(2);
117
+ }
118
+ }
119
+
120
+ const files = runGit(['ls-files']).split('\n').map((s) => s.trim()).filter(Boolean);
121
+ const forbidden = [];
122
+ for (const p of files) {
123
+ if (isForbiddenTrackedPath(p)) forbidden.push(p);
124
+ if (!p.includes('/') && isForbiddenRootFile(p)) forbidden.push(p);
125
+ }
126
+
127
+ if (forbidden.length) {
128
+ console.error('[repo-sanity] forbidden tracked files detected:');
129
+ for (const p of Array.from(new Set(forbidden)).sort()) {
130
+ console.error(`- ${p}`);
131
+ }
132
+ process.exit(2);
133
+ }
134
+
135
+ checkRootLayout();
136
+ checkUntrackedNotIgnored();
137
+
138
+ console.log('[repo-sanity] ok');
@@ -6,6 +6,7 @@ import os from 'os';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { spawn } from 'child_process';
8
8
  import { setTimeout as delay } from 'node:timers/promises';
9
+ import http from 'node:http';
9
10
 
10
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
12
  const PROJECT_ROOT = path.resolve(__dirname, '../..');
@@ -251,7 +252,17 @@ async function waitForHealth(port, serverProc, timeoutMs = 20000) {
251
252
  try {
252
253
  const res = await fetch(`http://127.0.0.1:${port}/health`, { method: 'GET' });
253
254
  if (res.ok) {
254
- return;
255
+ // /health becomes reachable before runtime is fully initialized (server starts listening first
256
+ // to support token portal). Mock regressions must wait until hub pipeline is ready.
257
+ try {
258
+ const data = await res.json();
259
+ const ready = data && (data.ready === true || data.pipelineReady === true || data.status === 'ok');
260
+ if (ready) {
261
+ return;
262
+ }
263
+ } catch {
264
+ // ignore JSON errors, retry
265
+ }
255
266
  }
256
267
  } catch {
257
268
  // retry
@@ -276,6 +287,150 @@ async function stopServer(child, forceTimeout = 5000) {
276
287
  child.kill('SIGKILL');
277
288
  }
278
289
 
290
+ async function createLocalUpstreamServer(handler) {
291
+ return await new Promise((resolve, reject) => {
292
+ const server = http.createServer(handler);
293
+ server.on('error', reject);
294
+ server.listen(0, '127.0.0.1', () => {
295
+ const address = server.address();
296
+ if (!address || typeof address !== 'object') {
297
+ reject(new Error('Failed to obtain listen address for upstream server'));
298
+ return;
299
+ }
300
+ resolve({
301
+ server,
302
+ port: address.port
303
+ });
304
+ });
305
+ });
306
+ }
307
+
308
+ function buildIflowUaProbeConfig(port, upstreamPort) {
309
+ return {
310
+ version: '1.0.0',
311
+ virtualrouter: {
312
+ inputProtocol: 'openai',
313
+ outputProtocol: 'openai',
314
+ providers: {
315
+ iflow: {
316
+ id: 'iflow',
317
+ enabled: true,
318
+ type: 'iflow',
319
+ baseURL: `http://127.0.0.1:${upstreamPort}/v1`,
320
+ compatibilityProfile: 'chat:iflow',
321
+ auth: {
322
+ type: 'apikey',
323
+ apiKey: 'test-upstream-token'
324
+ },
325
+ models: {
326
+ 'glm-4.7': { supportsStreaming: false }
327
+ }
328
+ }
329
+ },
330
+ routing: {
331
+ default: ['iflow.glm-4.7']
332
+ }
333
+ },
334
+ httpserver: {
335
+ host: '127.0.0.1',
336
+ port
337
+ }
338
+ };
339
+ }
340
+
341
+ async function runIflowUserAgentRegression() {
342
+ const seen = {
343
+ path: '',
344
+ headers: {},
345
+ body: ''
346
+ };
347
+
348
+ const { server: upstream, port: upstreamPort } = await createLocalUpstreamServer(async (req, res) => {
349
+ try {
350
+ seen.path = String(req.url || '');
351
+ seen.headers = req.headers || {};
352
+ let raw = '';
353
+ req.setEncoding('utf8');
354
+ req.on('data', (chunk) => {
355
+ raw += chunk;
356
+ });
357
+ await new Promise((resolve) => req.on('end', resolve));
358
+ seen.body = raw;
359
+ } catch {
360
+ // ignore
361
+ }
362
+
363
+ res.writeHead(200, { 'Content-Type': 'application/json' });
364
+ res.end(
365
+ JSON.stringify({
366
+ id: 'chatcmpl_mock_iflow_ua',
367
+ object: 'chat.completion',
368
+ created: Math.floor(Date.now() / 1000),
369
+ model: 'glm-4.7',
370
+ choices: [
371
+ {
372
+ index: 0,
373
+ message: { role: 'assistant', content: 'ok' },
374
+ finish_reason: 'stop'
375
+ }
376
+ ],
377
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }
378
+ })
379
+ );
380
+ });
381
+
382
+ const port = 5750;
383
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'routecodex-iflow-ua-'));
384
+ const configPath = path.join(dir, 'config.json');
385
+ await fs.writeFile(configPath, JSON.stringify(buildIflowUaProbeConfig(port, upstreamPort), null, 2), 'utf-8');
386
+
387
+ const entry = path.join(PROJECT_ROOT, 'dist', 'index.js');
388
+ const child = spawn(process.execPath, [entry], {
389
+ cwd: PROJECT_ROOT,
390
+ env: {
391
+ ...process.env,
392
+ ROUTECODEX_PORT: String(port),
393
+ ROUTECODEX_CONFIG_PATH: configPath,
394
+ RCC_PORT: String(port),
395
+ RCC_CONFIG_PATH: configPath
396
+ },
397
+ stdio: ['ignore', 'pipe', 'pipe']
398
+ });
399
+
400
+ try {
401
+ await waitForHealth(port, child);
402
+ const res = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
403
+ method: 'POST',
404
+ headers: {
405
+ 'Content-Type': 'application/json',
406
+ // If UA precedence is wrong, this inbound UA will leak to upstream and break iFlow glm-4.7.
407
+ 'User-Agent': 'curl/8.7.1'
408
+ },
409
+ body: JSON.stringify({
410
+ model: 'iflow.glm-4.7',
411
+ messages: [{ role: 'user', content: 'hi' }],
412
+ max_tokens: 16,
413
+ stream: false
414
+ })
415
+ });
416
+ const text = await res.text();
417
+ if (!res.ok) {
418
+ throw new Error(`ua probe request failed: HTTP ${res.status}: ${text}`);
419
+ }
420
+
421
+ const upstreamUa = typeof seen.headers['user-agent'] === 'string' ? seen.headers['user-agent'] : '';
422
+ if (upstreamUa !== 'iFlow-Cli') {
423
+ throw new Error(
424
+ `iflow UA regression: expected upstream user-agent="iFlow-Cli", got ${JSON.stringify(upstreamUa)} (path=${seen.path})`
425
+ );
426
+ }
427
+ } finally {
428
+ await stopServer(child);
429
+ await fs.rm(dir, { recursive: true, force: true });
430
+ await new Promise((resolve) => upstream.close(() => resolve()));
431
+ }
432
+ }
433
+
279
434
  function collectInvalidNames(payload) {
280
435
  const failures = [];
281
436
  const check = (value, location) => {
@@ -546,6 +701,7 @@ async function runSample(sample, index) {
546
701
 
547
702
  async function main() {
548
703
  await ensureCliAvailable();
704
+ await runIflowUserAgentRegression();
549
705
  const samples = await loadRegistry();
550
706
  const watchedTags = new Set(['invalid_name', 'missing_output', 'missing_tool_call_id', 'require_fc_call_ids', 'regression']);
551
707
  const regressionSamples = samples.filter(