@jsonstudio/rcc 0.89.168 → 0.89.524

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 (431) hide show
  1. package/README.md +18 -15
  2. package/dist/build-info.js +3 -3
  3. package/dist/build-info.js.map +1 -1
  4. package/dist/cli.js +94 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +28 -5
  7. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  8. package/dist/commands/token-daemon.d.ts +2 -0
  9. package/dist/commands/token-daemon.js +183 -0
  10. package/dist/commands/token-daemon.js.map +1 -0
  11. package/dist/error-handling/quiet-error-handling-center.d.ts +9 -0
  12. package/dist/error-handling/quiet-error-handling-center.js +141 -0
  13. package/dist/error-handling/quiet-error-handling-center.js.map +1 -0
  14. package/dist/error-handling/route-error-hub.js +8 -2
  15. package/dist/error-handling/route-error-hub.js.map +1 -1
  16. package/dist/index.js +4 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/modules/llmswitch/bridge.d.ts +1 -1
  19. package/dist/modules/llmswitch/bridge.js +3 -2
  20. package/dist/modules/llmswitch/bridge.js.map +1 -1
  21. package/dist/modules/pipeline/utils/colored-logger.d.ts +2 -0
  22. package/dist/modules/pipeline/utils/colored-logger.js +22 -3
  23. package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
  24. package/dist/providers/auth/antigravity-userinfo-helper.d.ts +10 -0
  25. package/dist/providers/auth/antigravity-userinfo-helper.js +140 -0
  26. package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -0
  27. package/dist/providers/auth/gemini-cli-userinfo-helper.js +12 -2
  28. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  29. package/dist/providers/auth/oauth-lifecycle.js +395 -24
  30. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  31. package/dist/providers/auth/token-scanner/index.d.ts +32 -0
  32. package/dist/providers/auth/token-scanner/index.js +86 -0
  33. package/dist/providers/auth/token-scanner/index.js.map +1 -0
  34. package/dist/providers/auth/tokenfile-auth.d.ts +17 -0
  35. package/dist/providers/auth/tokenfile-auth.js +27 -5
  36. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  37. package/dist/providers/core/api/provider-types.d.ts +10 -0
  38. package/dist/providers/core/config/oauth-flows.d.ts +25 -0
  39. package/dist/providers/core/config/oauth-flows.js +92 -5
  40. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  41. package/dist/providers/core/config/provider-oauth-configs.js +93 -2
  42. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  43. package/dist/providers/core/config/service-profiles.js +18 -10
  44. package/dist/providers/core/config/service-profiles.js.map +1 -1
  45. package/dist/providers/core/runtime/base-provider.d.ts +2 -0
  46. package/dist/providers/core/runtime/base-provider.js +135 -15
  47. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  48. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +8 -3
  49. package/dist/providers/core/runtime/gemini-cli-http-provider.js +332 -67
  50. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  51. package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
  52. package/dist/providers/core/runtime/http-request-executor.js +41 -1
  53. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  54. package/dist/providers/core/runtime/http-transport-provider.d.ts +27 -0
  55. package/dist/providers/core/runtime/http-transport-provider.js +342 -69
  56. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  57. package/dist/providers/core/runtime/provider-error-classifier.d.ts +2 -2
  58. package/dist/providers/core/runtime/provider-error-classifier.js +14 -4
  59. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  60. package/dist/providers/core/runtime/provider-factory.d.ts +1 -0
  61. package/dist/providers/core/runtime/provider-factory.js +37 -8
  62. package/dist/providers/core/runtime/provider-factory.js.map +1 -1
  63. package/dist/providers/core/runtime/responses-provider.d.ts +3 -3
  64. package/dist/providers/core/runtime/responses-provider.js +56 -117
  65. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  66. package/dist/providers/core/runtime/vision-debug-utils.d.ts +13 -0
  67. package/dist/providers/core/runtime/vision-debug-utils.js +114 -0
  68. package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -0
  69. package/dist/providers/core/strategies/oauth-auth-code-flow.js +82 -25
  70. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  71. package/dist/providers/core/utils/http-client.d.ts +5 -0
  72. package/dist/providers/core/utils/http-client.js +31 -4
  73. package/dist/providers/core/utils/http-client.js.map +1 -1
  74. package/dist/providers/core/utils/provider-error-reporter.js +8 -2
  75. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  76. package/dist/providers/core/utils/snapshot-writer.d.ts +1 -1
  77. package/dist/providers/core/utils/snapshot-writer.js +5 -1
  78. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  79. package/dist/providers/profile/provider-profile-loader.js +8 -4
  80. package/dist/providers/profile/provider-profile-loader.js.map +1 -1
  81. package/dist/runtime/runtime-flags.d.ts +4 -0
  82. package/dist/runtime/runtime-flags.js +32 -0
  83. package/dist/runtime/runtime-flags.js.map +1 -0
  84. package/dist/server/handlers/handler-utils.js +29 -2
  85. package/dist/server/handlers/handler-utils.js.map +1 -1
  86. package/dist/server/handlers/messages-handler.js +27 -26
  87. package/dist/server/handlers/messages-handler.js.map +1 -1
  88. package/dist/server/handlers/responses-handler.js +35 -1
  89. package/dist/server/handlers/responses-handler.js.map +1 -1
  90. package/dist/server/handlers/sse-dispatcher.js +22 -2
  91. package/dist/server/handlers/sse-dispatcher.js.map +1 -1
  92. package/dist/server/runtime/http-server/index.d.ts +10 -0
  93. package/dist/server/runtime/http-server/index.js +551 -148
  94. package/dist/server/runtime/http-server/index.js.map +1 -1
  95. package/dist/server/runtime/http-server/request-executor.d.ts +14 -0
  96. package/dist/server/runtime/http-server/request-executor.js +638 -149
  97. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  98. package/dist/server/runtime/http-server/routes.d.ts +5 -0
  99. package/dist/server/runtime/http-server/routes.js +69 -0
  100. package/dist/server/runtime/http-server/routes.js.map +1 -1
  101. package/dist/server/runtime/http-server/runtime-manager.js +18 -0
  102. package/dist/server/runtime/http-server/runtime-manager.js.map +1 -1
  103. package/dist/server/utils/sse-request-parser.d.ts +1 -0
  104. package/dist/server/utils/sse-request-parser.js +17 -6
  105. package/dist/server/utils/sse-request-parser.js.map +1 -1
  106. package/dist/server/utils/utf8-chunk-buffer.d.ts +43 -0
  107. package/dist/server/utils/utf8-chunk-buffer.js +132 -0
  108. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -0
  109. package/dist/server/utils/warmup-detector.d.ts +7 -0
  110. package/dist/server/utils/warmup-detector.js +125 -0
  111. package/dist/server/utils/warmup-detector.js.map +1 -0
  112. package/dist/server/utils/warmup-storm-tracker.d.ts +9 -0
  113. package/dist/server/utils/warmup-storm-tracker.js +61 -0
  114. package/dist/server/utils/warmup-storm-tracker.js.map +1 -0
  115. package/dist/token-daemon/index.d.ts +7 -0
  116. package/dist/token-daemon/index.js +242 -0
  117. package/dist/token-daemon/index.js.map +1 -0
  118. package/dist/token-daemon/server-utils.d.ts +33 -0
  119. package/dist/token-daemon/server-utils.js +155 -0
  120. package/dist/token-daemon/server-utils.js.map +1 -0
  121. package/dist/token-daemon/token-daemon.d.ts +20 -0
  122. package/dist/token-daemon/token-daemon.js +144 -0
  123. package/dist/token-daemon/token-daemon.js.map +1 -0
  124. package/dist/token-daemon/token-types.d.ts +44 -0
  125. package/dist/token-daemon/token-types.js +18 -0
  126. package/dist/token-daemon/token-types.js.map +1 -0
  127. package/dist/token-daemon/token-utils.d.ts +17 -0
  128. package/dist/token-daemon/token-utils.js +153 -0
  129. package/dist/token-daemon/token-utils.js.map +1 -0
  130. package/dist/tools/semantic-replay.js +7 -6
  131. package/dist/tools/semantic-replay.js.map +1 -1
  132. package/dist/utils/debug-utils.js +14 -0
  133. package/dist/utils/debug-utils.js.map +1 -1
  134. package/dist/utils/error-handler-registry.d.ts +36 -0
  135. package/dist/utils/error-handler-registry.js +99 -12
  136. package/dist/utils/error-handler-registry.js.map +1 -1
  137. package/dist/utils/error-handling-utils.js +4 -3
  138. package/dist/utils/error-handling-utils.js.map +1 -1
  139. package/dist/utils/log-helpers.d.ts +6 -0
  140. package/dist/utils/log-helpers.js +90 -0
  141. package/dist/utils/log-helpers.js.map +1 -0
  142. package/dist/utils/logger.d.ts +8 -0
  143. package/dist/utils/logger.js +55 -2
  144. package/dist/utils/logger.js.map +1 -1
  145. package/dist/utils/snapshot-writer.js +2 -6
  146. package/dist/utils/snapshot-writer.js.map +1 -1
  147. package/node_modules/@jsonstudio/llms/README.md +2 -0
  148. package/node_modules/@jsonstudio/llms/dist/conversion/codecs/gemini-openai-codec.js +152 -6
  149. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/auto-thinking.d.ts +6 -0
  150. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/auto-thinking.js +25 -0
  151. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/field-mapping.d.ts +14 -0
  152. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/field-mapping.js +155 -0
  153. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
  154. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.js +68 -0
  155. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
  156. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.js +83 -0
  157. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-tool-extraction.d.ts +2 -0
  158. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-tool-extraction.js +264 -0
  159. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
  160. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
  161. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
  162. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.js +63 -0
  163. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwen-transform.d.ts +3 -0
  164. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwen-transform.js +209 -0
  165. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/request-rules.d.ts +24 -0
  166. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/request-rules.js +63 -0
  167. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/response-blacklist.d.ts +14 -0
  168. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/response-blacklist.js +85 -0
  169. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/response-normalize.d.ts +5 -0
  170. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/response-normalize.js +121 -0
  171. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/response-validate.d.ts +5 -0
  172. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/response-validate.js +76 -0
  173. package/{dist/providers/compat/utils/snapshot-writer.d.ts → node_modules/@jsonstudio/llms/dist/conversion/compat/actions/snapshot.d.ts} +2 -2
  174. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/snapshot.js +21 -0
  175. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/tool-schema.d.ts +6 -0
  176. package/{dist/providers/compat/glm/utils/tool-schema-helpers.js → node_modules/@jsonstudio/llms/dist/conversion/compat/actions/tool-schema.js} +6 -1
  177. package/{dist/providers/compat/filters → node_modules/@jsonstudio/llms/dist/conversion/compat/actions}/universal-shape-filter.d.ts +17 -22
  178. package/{dist/providers/compat/filters → node_modules/@jsonstudio/llms/dist/conversion/compat/actions}/universal-shape-filter.js +46 -99
  179. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-gemini.json +17 -0
  180. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +196 -13
  181. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +194 -26
  182. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +43 -35
  183. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +20 -16
  184. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  185. package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +1 -1
  186. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
  187. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.js +5 -665
  188. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.d.ts +9 -0
  189. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +869 -0
  190. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +55 -0
  191. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
  192. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +74 -5
  193. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  194. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/target-utils.js +9 -0
  195. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +213 -1
  196. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.d.ts +34 -0
  197. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +84 -24
  198. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/response-runtime.js +19 -2
  199. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
  200. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.js +383 -0
  201. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/gemini-mapper.js +241 -14
  202. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
  203. package/node_modules/@jsonstudio/llms/dist/conversion/hub/standardized-bridge.js +14 -0
  204. package/node_modules/@jsonstudio/llms/dist/conversion/hub/types/standardized.d.ts +1 -0
  205. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-host-policy.d.ts +6 -0
  206. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-host-policy.js +14 -0
  207. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +133 -5
  208. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils.js +98 -3
  209. package/node_modules/@jsonstudio/llms/dist/conversion/shared/bridge-message-utils.js +137 -10
  210. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-output-builder.js +43 -2
  211. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-reasoning-registry.d.ts +4 -0
  212. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-reasoning-registry.js +62 -1
  213. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-response-utils.js +23 -1
  214. package/node_modules/@jsonstudio/llms/dist/conversion/shared/snapshot-utils.js +17 -47
  215. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-canonicalizer.d.ts +2 -0
  216. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +12 -0
  217. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +25 -2
  218. package/node_modules/@jsonstudio/llms/dist/index.d.ts +1 -0
  219. package/node_modules/@jsonstudio/llms/dist/index.js +1 -0
  220. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +540 -36
  221. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +12 -11
  222. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.d.ts +19 -0
  223. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.js +64 -0
  224. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +26 -0
  225. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +450 -54
  226. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +23 -458
  227. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/health-manager.js +2 -7
  228. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.d.ts +7 -0
  229. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.js +78 -0
  230. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/provider-registry.js +7 -2
  231. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +14 -3
  232. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-estimator.d.ts +2 -0
  233. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-estimator.js +16 -0
  234. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-file-scanner.d.ts +15 -0
  235. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-file-scanner.js +56 -0
  236. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/tool-signals.d.ts +13 -0
  237. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/tool-signals.js +403 -0
  238. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +86 -2
  239. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +3 -1
  240. package/node_modules/@jsonstudio/llms/dist/servertool/engine.d.ts +27 -0
  241. package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +60 -0
  242. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.d.ts +40 -0
  243. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.js +1 -0
  244. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.d.ts +1 -0
  245. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +194 -0
  246. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.d.ts +1 -0
  247. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.js +638 -0
  248. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.d.ts +33 -0
  249. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.js +1 -0
  250. package/node_modules/@jsonstudio/llms/dist/servertool/registry.d.ts +18 -0
  251. package/node_modules/@jsonstudio/llms/dist/servertool/registry.js +27 -0
  252. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.d.ts +8 -0
  253. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +208 -0
  254. package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +88 -0
  255. package/node_modules/@jsonstudio/llms/dist/servertool/types.js +1 -0
  256. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.d.ts +2 -0
  257. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.js +185 -0
  258. package/node_modules/@jsonstudio/llms/dist/sse/json-to-sse/event-generators/responses.js +15 -3
  259. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +6 -3
  260. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
  261. package/node_modules/@jsonstudio/llms/dist/sse/types/gemini-types.d.ts +20 -1
  262. package/node_modules/@jsonstudio/llms/dist/sse/types/responses-types.js +1 -1
  263. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.d.ts +73 -0
  264. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.js +280 -0
  265. package/node_modules/@jsonstudio/llms/package.json +2 -2
  266. package/package.json +11 -10
  267. package/scripts/README.md +26 -12
  268. package/scripts/auth-antigravity-token.mjs +64 -0
  269. package/scripts/auth-gemini-cli-token.mjs +96 -0
  270. package/scripts/auth-iflow-manual.mjs +81 -0
  271. package/scripts/auth-iflow-token-direct.mjs +87 -0
  272. package/scripts/auth-iflow-token.mjs +77 -0
  273. package/scripts/copy-compat-assets.mjs +3 -15
  274. package/scripts/install-verify.mjs +1 -0
  275. package/scripts/pack-mode.mjs +2 -1
  276. package/scripts/publish-rcc.mjs +20 -4
  277. package/scripts/replay-codex-sample.mjs +13 -8
  278. package/scripts/tests/chat-pipeline-blackbox.mjs +1 -1
  279. package/scripts/tests/virtual-router-health.mjs +141 -6
  280. package/scripts/tools/capture-provider-goldens.mjs +8 -7
  281. package/scripts/verify-client-headers.mjs +224 -0
  282. package/dist/providers/compat/base-compatibility.d.ts +0 -27
  283. package/dist/providers/compat/base-compatibility.js +0 -143
  284. package/dist/providers/compat/base-compatibility.js.map +0 -1
  285. package/dist/providers/compat/compat-directory-loader.d.ts +0 -4
  286. package/dist/providers/compat/compat-directory-loader.js +0 -85
  287. package/dist/providers/compat/compat-directory-loader.js.map +0 -1
  288. package/dist/providers/compat/compatibility-adapter.d.ts +0 -18
  289. package/dist/providers/compat/compatibility-adapter.js +0 -104
  290. package/dist/providers/compat/compatibility-adapter.js.map +0 -1
  291. package/dist/providers/compat/compatibility-factory.d.ts +0 -57
  292. package/dist/providers/compat/compatibility-factory.js +0 -155
  293. package/dist/providers/compat/compatibility-factory.js.map +0 -1
  294. package/dist/providers/compat/compatibility-interface.d.ts +0 -35
  295. package/dist/providers/compat/compatibility-interface.js +0 -2
  296. package/dist/providers/compat/compatibility-interface.js.map +0 -1
  297. package/dist/providers/compat/compatibility-manager.d.ts +0 -85
  298. package/dist/providers/compat/compatibility-manager.js +0 -368
  299. package/dist/providers/compat/compatibility-manager.js.map +0 -1
  300. package/dist/providers/compat/config/config-compatibility.d.ts +0 -28
  301. package/dist/providers/compat/config/config-compatibility.js +0 -95
  302. package/dist/providers/compat/config/config-compatibility.js.map +0 -1
  303. package/dist/providers/compat/field-mapping.d.ts +0 -102
  304. package/dist/providers/compat/field-mapping.js +0 -447
  305. package/dist/providers/compat/field-mapping.js.map +0 -1
  306. package/dist/providers/compat/filters/blacklist-sanitizer.d.ts +0 -45
  307. package/dist/providers/compat/filters/blacklist-sanitizer.js +0 -133
  308. package/dist/providers/compat/filters/blacklist-sanitizer.js.map +0 -1
  309. package/dist/providers/compat/filters/response-blacklist-sanitizer.d.ts +0 -28
  310. package/dist/providers/compat/filters/response-blacklist-sanitizer.js +0 -138
  311. package/dist/providers/compat/filters/response-blacklist-sanitizer.js.map +0 -1
  312. package/dist/providers/compat/filters/universal-shape-filter.js.map +0 -1
  313. package/dist/providers/compat/glm/config/blacklist-rules.json +0 -22
  314. package/dist/providers/compat/glm/config/field-mappings.json +0 -92
  315. package/dist/providers/compat/glm/config/response-blacklist.json +0 -7
  316. package/dist/providers/compat/glm/config/shape-filters.json +0 -37
  317. package/dist/providers/compat/glm/field-mapping/field-mapping-processor.d.ts +0 -28
  318. package/dist/providers/compat/glm/field-mapping/field-mapping-processor.js +0 -306
  319. package/dist/providers/compat/glm/field-mapping/field-mapping-processor.js.map +0 -1
  320. package/dist/providers/compat/glm/functions/glm-processor.d.ts +0 -50
  321. package/dist/providers/compat/glm/functions/glm-processor.js +0 -134
  322. package/dist/providers/compat/glm/functions/glm-processor.js.map +0 -1
  323. package/dist/providers/compat/glm/glm-compatibility.d.ts +0 -34
  324. package/dist/providers/compat/glm/glm-compatibility.js +0 -117
  325. package/dist/providers/compat/glm/glm-compatibility.js.map +0 -1
  326. package/dist/providers/compat/glm/hooks/base-hook.d.ts +0 -21
  327. package/dist/providers/compat/glm/hooks/base-hook.js +0 -53
  328. package/dist/providers/compat/glm/hooks/base-hook.js.map +0 -1
  329. package/dist/providers/compat/glm/hooks/glm-request-validation-hook.d.ts +0 -24
  330. package/dist/providers/compat/glm/hooks/glm-request-validation-hook.js +0 -268
  331. package/dist/providers/compat/glm/hooks/glm-request-validation-hook.js.map +0 -1
  332. package/dist/providers/compat/glm/hooks/glm-response-normalization-hook.d.ts +0 -21
  333. package/dist/providers/compat/glm/hooks/glm-response-normalization-hook.js +0 -171
  334. package/dist/providers/compat/glm/hooks/glm-response-normalization-hook.js.map +0 -1
  335. package/dist/providers/compat/glm/hooks/glm-response-validation-hook.d.ts +0 -25
  336. package/dist/providers/compat/glm/hooks/glm-response-validation-hook.js +0 -236
  337. package/dist/providers/compat/glm/hooks/glm-response-validation-hook.js.map +0 -1
  338. package/dist/providers/compat/glm/hooks/glm-tool-cleaning-hook.d.ts +0 -26
  339. package/dist/providers/compat/glm/hooks/glm-tool-cleaning-hook.js +0 -186
  340. package/dist/providers/compat/glm/hooks/glm-tool-cleaning-hook.js.map +0 -1
  341. package/dist/providers/compat/glm/index.d.ts +0 -24
  342. package/dist/providers/compat/glm/index.js +0 -29
  343. package/dist/providers/compat/glm/index.js.map +0 -1
  344. package/dist/providers/compat/glm/utils/tool-schema-helpers.d.ts +0 -3
  345. package/dist/providers/compat/glm/utils/tool-schema-helpers.js.map +0 -1
  346. package/dist/providers/compat/iflow/config/field-mappings.json +0 -92
  347. package/dist/providers/compat/iflow/config/shape-filters.json +0 -37
  348. package/dist/providers/compat/iflow/field-mapping/iflow-field-mapping-processor.d.ts +0 -34
  349. package/dist/providers/compat/iflow/field-mapping/iflow-field-mapping-processor.js +0 -386
  350. package/dist/providers/compat/iflow/field-mapping/iflow-field-mapping-processor.js.map +0 -1
  351. package/dist/providers/compat/iflow/functions/iflow-processor.d.ts +0 -53
  352. package/dist/providers/compat/iflow/functions/iflow-processor.js +0 -215
  353. package/dist/providers/compat/iflow/functions/iflow-processor.js.map +0 -1
  354. package/dist/providers/compat/iflow/hooks/base-hook.d.ts +0 -23
  355. package/dist/providers/compat/iflow/hooks/base-hook.js +0 -59
  356. package/dist/providers/compat/iflow/hooks/base-hook.js.map +0 -1
  357. package/dist/providers/compat/iflow/hooks/iflow-request-validation-hook.d.ts +0 -23
  358. package/dist/providers/compat/iflow/hooks/iflow-request-validation-hook.js +0 -279
  359. package/dist/providers/compat/iflow/hooks/iflow-request-validation-hook.js.map +0 -1
  360. package/dist/providers/compat/iflow/hooks/iflow-response-normalization-hook.d.ts +0 -20
  361. package/dist/providers/compat/iflow/hooks/iflow-response-normalization-hook.js +0 -180
  362. package/dist/providers/compat/iflow/hooks/iflow-response-normalization-hook.js.map +0 -1
  363. package/dist/providers/compat/iflow/hooks/iflow-response-validation-hook.d.ts +0 -23
  364. package/dist/providers/compat/iflow/hooks/iflow-response-validation-hook.js +0 -232
  365. package/dist/providers/compat/iflow/hooks/iflow-response-validation-hook.js.map +0 -1
  366. package/dist/providers/compat/iflow/hooks/iflow-tool-cleaning-hook.d.ts +0 -25
  367. package/dist/providers/compat/iflow/hooks/iflow-tool-cleaning-hook.js +0 -216
  368. package/dist/providers/compat/iflow/hooks/iflow-tool-cleaning-hook.js.map +0 -1
  369. package/dist/providers/compat/iflow/iflow-compatibility.d.ts +0 -24
  370. package/dist/providers/compat/iflow/iflow-compatibility.js +0 -94
  371. package/dist/providers/compat/iflow/iflow-compatibility.js.map +0 -1
  372. package/dist/providers/compat/index.d.ts +0 -59
  373. package/dist/providers/compat/index.js +0 -83
  374. package/dist/providers/compat/index.js.map +0 -1
  375. package/dist/providers/compat/lmstudio-compatibility.d.ts +0 -44
  376. package/dist/providers/compat/lmstudio-compatibility.js +0 -193
  377. package/dist/providers/compat/lmstudio-compatibility.js.map +0 -1
  378. package/dist/providers/compat/passthrough-compatibility.d.ts +0 -29
  379. package/dist/providers/compat/passthrough-compatibility.js +0 -83
  380. package/dist/providers/compat/passthrough-compatibility.js.map +0 -1
  381. package/dist/providers/compat/profiles/chat/glm/index.d.ts +0 -6
  382. package/dist/providers/compat/profiles/chat/glm/index.js +0 -6
  383. package/dist/providers/compat/profiles/chat/glm/index.js.map +0 -1
  384. package/dist/providers/compat/profiles/chat/iflow/index.d.ts +0 -6
  385. package/dist/providers/compat/profiles/chat/iflow/index.js +0 -6
  386. package/dist/providers/compat/profiles/chat/iflow/index.js.map +0 -1
  387. package/dist/providers/compat/profiles/chat/lmstudio/index.d.ts +0 -6
  388. package/dist/providers/compat/profiles/chat/lmstudio/index.js +0 -6
  389. package/dist/providers/compat/profiles/chat/lmstudio/index.js.map +0 -1
  390. package/dist/providers/compat/profiles/chat/qwen/index.d.ts +0 -6
  391. package/dist/providers/compat/profiles/chat/qwen/index.js +0 -6
  392. package/dist/providers/compat/profiles/chat/qwen/index.js.map +0 -1
  393. package/dist/providers/compat/profiles/compat/passthrough/index.d.ts +0 -6
  394. package/dist/providers/compat/profiles/compat/passthrough/index.js +0 -6
  395. package/dist/providers/compat/profiles/compat/passthrough/index.js.map +0 -1
  396. package/dist/providers/compat/profiles/responses/c4m/index.d.ts +0 -6
  397. package/dist/providers/compat/profiles/responses/c4m/index.js +0 -6
  398. package/dist/providers/compat/profiles/responses/c4m/index.js.map +0 -1
  399. package/dist/providers/compat/profiles/responses/default/index.d.ts +0 -6
  400. package/dist/providers/compat/profiles/responses/default/index.js +0 -6
  401. package/dist/providers/compat/profiles/responses/default/index.js.map +0 -1
  402. package/dist/providers/compat/profiles/responses/fai/index.d.ts +0 -6
  403. package/dist/providers/compat/profiles/responses/fai/index.js +0 -6
  404. package/dist/providers/compat/profiles/responses/fai/index.js.map +0 -1
  405. package/dist/providers/compat/profiles/responses/fc/index.d.ts +0 -6
  406. package/dist/providers/compat/profiles/responses/fc/index.js +0 -6
  407. package/dist/providers/compat/profiles/responses/fc/index.js.map +0 -1
  408. package/dist/providers/compat/qwen/index.d.ts +0 -4
  409. package/dist/providers/compat/qwen/index.js +0 -6
  410. package/dist/providers/compat/qwen/index.js.map +0 -1
  411. package/dist/providers/compat/qwen-compatibility.d.ts +0 -52
  412. package/dist/providers/compat/qwen-compatibility.js +0 -330
  413. package/dist/providers/compat/qwen-compatibility.js.map +0 -1
  414. package/dist/providers/compat/register-compat-module.d.ts +0 -8
  415. package/dist/providers/compat/register-compat-module.js +0 -53
  416. package/dist/providers/compat/register-compat-module.js.map +0 -1
  417. package/dist/providers/compat/responses/c4m-responses-compatibility.d.ts +0 -27
  418. package/dist/providers/compat/responses/c4m-responses-compatibility.js +0 -197
  419. package/dist/providers/compat/responses/c4m-responses-compatibility.js.map +0 -1
  420. package/dist/providers/compat/standard-compatibility-utils.d.ts +0 -1
  421. package/dist/providers/compat/standard-compatibility-utils.js +0 -77
  422. package/dist/providers/compat/standard-compatibility-utils.js.map +0 -1
  423. package/dist/providers/compat/standard-compatibility.d.ts +0 -31
  424. package/dist/providers/compat/standard-compatibility.js +0 -118
  425. package/dist/providers/compat/standard-compatibility.js.map +0 -1
  426. package/dist/providers/compat/utils/snapshot-writer.js +0 -62
  427. package/dist/providers/compat/utils/snapshot-writer.js.map +0 -1
  428. package/dist/tools/replay-request.d.ts +0 -0
  429. package/dist/tools/replay-request.js +0 -2
  430. package/dist/tools/replay-request.js.map +0 -1
  431. package/scripts/check-glm-compat.mjs +0 -47
@@ -3,16 +3,23 @@ import { ProviderRegistry } from './provider-registry.js';
3
3
  import { RouteLoadBalancer } from './load-balancer.js';
4
4
  import { RoutingClassifier } from './classifier.js';
5
5
  import { buildRoutingFeatures } from './features.js';
6
- import { DEFAULT_ROUTE, ROUTE_PRIORITY, VirtualRouterError, VirtualRouterErrorCode } from './types.js';
6
+ import { ContextAdvisor } from './context-advisor.js';
7
+ import { DEFAULT_MODEL_CONTEXT_TOKENS, DEFAULT_ROUTE, ROUTE_PRIORITY, VirtualRouterError, VirtualRouterErrorCode } from './types.js';
8
+ import { getStatsCenter } from '../../telemetry/stats-center.js';
7
9
  export class VirtualRouterEngine {
8
10
  routing = {};
9
11
  providerRegistry = new ProviderRegistry();
10
12
  healthManager = new ProviderHealthManager();
11
13
  loadBalancer = new RouteLoadBalancer();
12
14
  classifier = new RoutingClassifier({});
15
+ contextAdvisor = new ContextAdvisor();
16
+ contextRouting;
13
17
  routeStats = new Map();
14
18
  debug = console; // thin hook; host may monkey-patch for colored logging
15
19
  healthConfig = null;
20
+ statsCenter = getStatsCenter();
21
+ // Derived flags from VirtualRouterConfig/routing used by process / response layers.
22
+ webSearchForce = false;
16
23
  initialize(config) {
17
24
  this.validateConfig(config);
18
25
  this.routing = config.routing;
@@ -22,6 +29,9 @@ export class VirtualRouterEngine {
22
29
  this.healthManager.registerProviders(Object.keys(config.providers));
23
30
  this.loadBalancer = new RouteLoadBalancer(config.loadBalancing);
24
31
  this.classifier = new RoutingClassifier(config.classifier);
32
+ this.contextRouting = config.contextRouting ?? { warnRatio: 0.9, hardLimit: false };
33
+ this.contextAdvisor.configure(this.contextRouting);
34
+ this.webSearchForce = config.webSearch?.force === true;
25
35
  this.routeStats = new Map();
26
36
  for (const routeName of Object.keys(this.routing)) {
27
37
  this.routeStats.set(routeName, { hits: 0 });
@@ -29,21 +39,56 @@ export class VirtualRouterEngine {
29
39
  }
30
40
  route(request, metadata) {
31
41
  const features = buildRoutingFeatures(request, metadata);
32
- const classification = this.classifier.classify(features);
33
- const routeName = classification.routeName || DEFAULT_ROUTE;
34
- const selection = this.selectProvider(routeName, metadata, classification);
35
- const target = this.providerRegistry.buildTarget(selection.providerKey);
42
+ const classification = metadata.routeHint && metadata.routeHint.trim()
43
+ ? {
44
+ routeName: metadata.routeHint.trim(),
45
+ confidence: 1,
46
+ reasoning: `route_hint:${metadata.routeHint.trim()}`,
47
+ fallback: false,
48
+ candidates: [metadata.routeHint.trim()]
49
+ }
50
+ : this.classifier.classify(features);
51
+ const requestedRoute = this.normalizeRouteAlias(classification.routeName || DEFAULT_ROUTE);
52
+ const selection = this.selectProvider(requestedRoute, metadata, classification, features);
53
+ const baseTarget = this.providerRegistry.buildTarget(selection.providerKey);
54
+ const forceVision = this.routeHasForceFlag('vision');
55
+ const target = {
56
+ ...baseTarget,
57
+ ...(this.webSearchForce ? { forceWebSearch: true } : {}),
58
+ ...(forceVision ? { forceVision: true } : {})
59
+ };
36
60
  this.healthManager.recordSuccess(selection.providerKey);
37
61
  this.incrementRouteStat(selection.routeUsed, selection.providerKey);
38
- const hitReason = this.buildHitReason(selection.routeUsed, classification, features);
39
- this.debug?.log?.('[virtual-router-hit]', selection.routeUsed, selection.providerKey, target.modelId || '', hitReason ? `reason=${hitReason}` : '');
40
- const didFallback = selection.routeUsed !== routeName || classification.fallback;
62
+ try {
63
+ this.statsCenter.recordVirtualRouterHit({
64
+ requestId: metadata.requestId,
65
+ timestamp: Date.now(),
66
+ entryEndpoint: metadata.entryEndpoint || '/v1/chat/completions',
67
+ routeName: selection.routeUsed,
68
+ pool: selection.poolId || selection.routeUsed,
69
+ providerKey: selection.providerKey,
70
+ modelId: target.modelId || undefined
71
+ });
72
+ }
73
+ catch {
74
+ // stats must never break routing
75
+ }
76
+ const hitReason = this.buildHitReason(selection.routeUsed, selection.providerKey, classification, features);
77
+ const formatted = this.formatVirtualRouterHit(selection.routeUsed, selection.poolId, selection.providerKey, target.modelId || '', hitReason);
78
+ if (formatted) {
79
+ this.debug?.log?.(formatted);
80
+ }
81
+ else {
82
+ this.debug?.log?.('[virtual-router-hit]', selection.routeUsed, selection.providerKey, target.modelId || '', hitReason ? `reason=${hitReason}` : '');
83
+ }
84
+ const didFallback = selection.routeUsed !== requestedRoute;
41
85
  return {
42
86
  target,
43
87
  decision: {
44
88
  routeName: selection.routeUsed,
45
89
  providerKey: selection.providerKey,
46
90
  pool: selection.pool,
91
+ poolId: selection.poolId,
47
92
  confidence: classification.confidence,
48
93
  reasoning: classification.reasoning,
49
94
  fallback: didFallback
@@ -52,6 +97,7 @@ export class VirtualRouterEngine {
52
97
  routeName: selection.routeUsed,
53
98
  providerKey: selection.providerKey,
54
99
  pool: selection.pool,
100
+ poolId: selection.poolId,
55
101
  reasoning: classification.reasoning,
56
102
  fallback: didFallback,
57
103
  confidence: classification.confidence
@@ -81,10 +127,10 @@ export class VirtualRouterEngine {
81
127
  }
82
128
  getStatus() {
83
129
  const routes = {};
84
- for (const [route, pool] of Object.entries(this.routing)) {
130
+ for (const [route, pools] of Object.entries(this.routing)) {
85
131
  const stats = this.routeStats.get(route) ?? { hits: 0 };
86
132
  routes[route] = {
87
- providers: [...pool],
133
+ providers: this.flattenPoolTargets(pools),
88
134
  hits: stats.hits,
89
135
  lastUsedProvider: stats.lastProvider
90
136
  };
@@ -94,6 +140,14 @@ export class VirtualRouterEngine {
94
140
  health: this.healthManager.getSnapshot()
95
141
  };
96
142
  }
143
+ /**
144
+ * 将分类器产生的逻辑路由名直接归一化为配置中的路由键。
145
+ * 不再维护 "websearch" 之类的别名,调用方应显式使用 "web_search" 或 "search" 等实际路由名。
146
+ */
147
+ normalizeRouteAlias(routeName) {
148
+ const base = routeName && routeName.trim() ? routeName.trim() : DEFAULT_ROUTE;
149
+ return base;
150
+ }
97
151
  validateConfig(config) {
98
152
  if (!config.routing || typeof config.routing !== 'object') {
99
153
  throw new VirtualRouterError('routing configuration is required', VirtualRouterErrorCode.CONFIG_ERROR);
@@ -101,47 +155,133 @@ export class VirtualRouterEngine {
101
155
  if (!config.providers || Object.keys(config.providers).length === 0) {
102
156
  throw new VirtualRouterError('providers configuration is required', VirtualRouterErrorCode.CONFIG_ERROR);
103
157
  }
104
- const defaultPool = config.routing[DEFAULT_ROUTE];
105
- if (!Array.isArray(defaultPool) || defaultPool.length === 0) {
158
+ const defaultPools = config.routing[DEFAULT_ROUTE];
159
+ if (!this.routeHasTargets(defaultPools)) {
106
160
  throw new VirtualRouterError('default route must be configured with at least one provider', VirtualRouterErrorCode.CONFIG_ERROR);
107
161
  }
162
+ if (!this.hasPrimaryPool(defaultPools)) {
163
+ throw new VirtualRouterError('default route must define at least one non-backup pool', VirtualRouterErrorCode.CONFIG_ERROR);
164
+ }
108
165
  const providerKeys = new Set(Object.keys(config.providers));
109
- for (const [routeName, pool] of Object.entries(config.routing)) {
110
- if (!Array.isArray(pool) || !pool.length) {
166
+ for (const [routeName, pools] of Object.entries(config.routing)) {
167
+ if (!this.routeHasTargets(pools)) {
111
168
  if (routeName === DEFAULT_ROUTE) {
112
169
  throw new VirtualRouterError('default route cannot be empty', VirtualRouterErrorCode.CONFIG_ERROR);
113
170
  }
114
171
  continue;
115
172
  }
116
- for (const providerKey of pool) {
117
- if (!providerKeys.has(providerKey)) {
118
- throw new VirtualRouterError(`Route ${routeName} references unknown provider ${providerKey}`, VirtualRouterErrorCode.CONFIG_ERROR);
173
+ for (const pool of pools) {
174
+ if (!Array.isArray(pool.targets) || !pool.targets.length) {
175
+ continue;
176
+ }
177
+ for (const providerKey of pool.targets) {
178
+ if (!providerKeys.has(providerKey)) {
179
+ throw new VirtualRouterError(`Route ${routeName} references unknown provider ${providerKey}`, VirtualRouterErrorCode.CONFIG_ERROR);
180
+ }
119
181
  }
120
182
  }
121
183
  }
122
184
  }
123
- selectProvider(requestedRoute, metadata, classification) {
124
- const candidates = this.buildRouteCandidates(requestedRoute, classification.candidates);
185
+ selectProvider(requestedRoute, metadata, classification, features) {
186
+ const candidates = this.buildRouteCandidates(requestedRoute, classification.candidates, features);
125
187
  const stickyKey = this.resolveStickyKey(metadata);
126
188
  const attempted = [];
127
- for (const routeName of candidates) {
128
- const pool = this.routing[routeName];
129
- if (!Array.isArray(pool) || pool.length === 0) {
130
- attempted.push(routeName);
189
+ const visitedRoutes = new Set();
190
+ const routeQueue = this.initializeRouteQueue(candidates);
191
+ const estimatedTokens = typeof features.estimatedTokens === 'number' && Number.isFinite(features.estimatedTokens)
192
+ ? Math.max(0, features.estimatedTokens)
193
+ : 0;
194
+ while (routeQueue.length) {
195
+ const routeName = routeQueue.shift();
196
+ if (visitedRoutes.has(routeName)) {
131
197
  continue;
132
198
  }
199
+ const routePools = this.routing[routeName];
200
+ if (!this.routeHasTargets(routePools)) {
201
+ visitedRoutes.add(routeName);
202
+ attempted.push(`${routeName}:empty`);
203
+ continue;
204
+ }
205
+ visitedRoutes.add(routeName);
206
+ const orderedPools = this.sortRoutePools(routePools);
207
+ for (const poolTier of orderedPools) {
208
+ const { providerKey, poolTargets, tierId, failureHint } = this.trySelectFromTier(routeName, poolTier, stickyKey, estimatedTokens, features);
209
+ if (providerKey) {
210
+ return { providerKey, routeUsed: routeName, pool: poolTargets, poolId: tierId };
211
+ }
212
+ if (failureHint) {
213
+ attempted.push(failureHint);
214
+ }
215
+ }
216
+ }
217
+ throw new VirtualRouterError(`All providers unavailable for route ${requestedRoute}`, VirtualRouterErrorCode.PROVIDER_NOT_AVAILABLE, { routeName: requestedRoute, attempted });
218
+ }
219
+ trySelectFromTier(routeName, tier, stickyKey, estimatedTokens, features) {
220
+ let targets = Array.isArray(tier.targets) ? tier.targets : [];
221
+ const serverToolRequired = features.metadata?.serverToolRequired === true;
222
+ if (serverToolRequired) {
223
+ const filtered = [];
224
+ for (const key of targets) {
225
+ try {
226
+ const profile = this.providerRegistry.get(key);
227
+ if (!profile.serverToolsDisabled) {
228
+ filtered.push(key);
229
+ }
230
+ }
231
+ catch {
232
+ // ignore unknown providers when filtering for servertools
233
+ }
234
+ }
235
+ targets = filtered;
236
+ }
237
+ // 当当前请求包含图片且路由为 default/thinking 时,优先在该路由池内选择
238
+ // Responses/Gemini 类型的 Provider,以便一次完成多模态推理;如果不存在则回退到原始列表。
239
+ if (features.hasImageAttachment && (routeName === DEFAULT_ROUTE || routeName === 'thinking')) {
240
+ const prioritized = [];
241
+ const fallthrough = [];
242
+ for (const key of targets) {
243
+ try {
244
+ const profile = this.providerRegistry.get(key);
245
+ if (profile.providerType === 'responses') {
246
+ prioritized.push(key);
247
+ }
248
+ else if (profile.providerType === 'gemini') {
249
+ prioritized.push(key);
250
+ }
251
+ else {
252
+ fallthrough.push(key);
253
+ }
254
+ }
255
+ catch {
256
+ fallthrough.push(key);
257
+ }
258
+ }
259
+ if (prioritized.length) {
260
+ targets = prioritized;
261
+ }
262
+ }
263
+ if (!targets.length) {
264
+ return { providerKey: null, poolTargets: [], tierId: tier.id, failureHint: `${routeName}:${tier.id}:empty` };
265
+ }
266
+ const contextResult = this.contextAdvisor.classify(targets, estimatedTokens, (key) => this.providerRegistry.get(key));
267
+ const prioritizedPools = this.buildContextCandidatePools(contextResult);
268
+ for (const candidatePool of prioritizedPools) {
133
269
  const providerKey = this.loadBalancer.select({
134
- routeName,
135
- candidates: pool,
270
+ routeName: `${routeName}:${tier.id}`,
271
+ candidates: candidatePool,
136
272
  stickyKey,
137
273
  availabilityCheck: (key) => this.healthManager.isAvailable(key)
138
274
  });
139
275
  if (providerKey) {
140
- return { providerKey, routeUsed: routeName, pool };
276
+ return { providerKey, poolTargets: tier.targets, tierId: tier.id };
141
277
  }
142
- attempted.push(routeName);
143
278
  }
144
- throw new VirtualRouterError(`All providers unavailable for route ${requestedRoute}`, VirtualRouterErrorCode.PROVIDER_NOT_AVAILABLE, { routeName: requestedRoute, attempted });
279
+ return {
280
+ providerKey: null,
281
+ poolTargets: tier.targets,
282
+ tierId: tier.id,
283
+ failureHint: this.describeAttempt(routeName, tier.id, contextResult)
284
+ };
145
285
  }
146
286
  incrementRouteStat(routeName, providerKey) {
147
287
  if (!this.routeStats.has(routeName)) {
@@ -155,6 +295,35 @@ export class VirtualRouterEngine {
155
295
  providerHealthConfig() {
156
296
  return this.healthManager.getConfig();
157
297
  }
298
+ initializeRouteQueue(candidates) {
299
+ return Array.from(new Set(candidates));
300
+ }
301
+ buildContextCandidatePools(result) {
302
+ const ordered = [];
303
+ if (result.safe.length) {
304
+ ordered.push(result.safe);
305
+ // 如果存在安全候选,直接放弃当前处于警戒阈值的模型
306
+ return ordered;
307
+ }
308
+ if (result.risky.length) {
309
+ ordered.push(result.risky);
310
+ }
311
+ // ratio >= 1 视为上下文溢出,直接标记为不可用
312
+ return ordered;
313
+ }
314
+ describeAttempt(routeName, poolId, result) {
315
+ const prefix = poolId ? `${routeName}:${poolId}` : routeName;
316
+ if (result.safe.length > 0) {
317
+ return `${prefix}:health`;
318
+ }
319
+ if (result.risky.length > 0) {
320
+ return `${prefix}:context_risky`;
321
+ }
322
+ if (result.overflow.length > 0) {
323
+ return `${prefix}:max_context_window`;
324
+ }
325
+ return prefix;
326
+ }
158
327
  resolveStickyKey(metadata) {
159
328
  const resume = metadata.responsesResume;
160
329
  if (resume && typeof resume.previousRequestId === 'string' && resume.previousRequestId.trim()) {
@@ -244,12 +413,32 @@ export class VirtualRouterEngine {
244
413
  return 'client_error';
245
414
  return 'unknown';
246
415
  }
247
- buildRouteCandidates(requestedRoute, classificationCandidates) {
248
- const normalized = requestedRoute || DEFAULT_ROUTE;
249
- const baseList = classificationCandidates && classificationCandidates.length
250
- ? classificationCandidates
251
- : [normalized];
252
- const ordered = this.sortByPriority(baseList);
416
+ buildRouteCandidates(requestedRoute, classificationCandidates, features) {
417
+ const forceVision = this.routeHasForceFlag('vision');
418
+ const normalized = this.normalizeRouteAlias(requestedRoute || DEFAULT_ROUTE);
419
+ const baseList = [];
420
+ if (classificationCandidates && classificationCandidates.length) {
421
+ for (const candidate of classificationCandidates) {
422
+ baseList.push(this.normalizeRouteAlias(candidate));
423
+ }
424
+ }
425
+ else if (normalized) {
426
+ baseList.push(normalized);
427
+ }
428
+ // 当检测到当前请求包含图片时,确保 default/thinking 也参与候选集,
429
+ // 以便优先尝试内建多模态模型(Responses/Gemini),再回落到 vision 路由池。
430
+ if (features.hasImageAttachment && !forceVision) {
431
+ const visionAwareRoutes = [DEFAULT_ROUTE, 'thinking'];
432
+ for (const routeName of visionAwareRoutes) {
433
+ if (this.routeHasTargets(this.routing[routeName]) && !baseList.includes(routeName)) {
434
+ baseList.push(routeName);
435
+ }
436
+ }
437
+ }
438
+ let ordered = this.sortByPriority(baseList);
439
+ if (features.hasImageAttachment && !forceVision) {
440
+ ordered = this.reorderForInlineVision(ordered);
441
+ }
253
442
  const deduped = [];
254
443
  for (const routeName of ordered) {
255
444
  if (routeName && !deduped.includes(routeName)) {
@@ -259,17 +448,59 @@ export class VirtualRouterEngine {
259
448
  if (!deduped.includes(DEFAULT_ROUTE)) {
260
449
  deduped.push(DEFAULT_ROUTE);
261
450
  }
262
- const filtered = deduped.filter((routeName) => {
263
- const pool = this.routing[routeName];
264
- return Array.isArray(pool) && pool.length > 0;
265
- });
266
- if (!filtered.includes(DEFAULT_ROUTE) &&
267
- Array.isArray(this.routing[DEFAULT_ROUTE]) &&
268
- this.routing[DEFAULT_ROUTE].length > 0) {
451
+ const filtered = deduped.filter((routeName) => this.routeHasTargets(this.routing[routeName]));
452
+ if (!filtered.includes(DEFAULT_ROUTE) && this.routeHasTargets(this.routing[DEFAULT_ROUTE])) {
269
453
  filtered.push(DEFAULT_ROUTE);
270
454
  }
271
455
  return filtered.length ? filtered : [DEFAULT_ROUTE];
272
456
  }
457
+ reorderForInlineVision(routeNames) {
458
+ const unique = Array.from(new Set(routeNames.filter(Boolean)));
459
+ if (!unique.length) {
460
+ return unique;
461
+ }
462
+ // 仅当 default/thinking 中存在 Responses/Gemini 提供方时,才将其提前作为「一次完成」优先级。
463
+ const inlinePreferred = [];
464
+ const inlineRoutes = [DEFAULT_ROUTE, 'thinking'];
465
+ for (const routeName of inlineRoutes) {
466
+ if (this.routeSupportsInlineVision(routeName) && !inlinePreferred.includes(routeName)) {
467
+ inlinePreferred.push(routeName);
468
+ }
469
+ }
470
+ if (!inlinePreferred.length) {
471
+ return unique;
472
+ }
473
+ const remaining = [];
474
+ for (const routeName of unique) {
475
+ if (!inlinePreferred.includes(routeName)) {
476
+ remaining.push(routeName);
477
+ }
478
+ }
479
+ return [...inlinePreferred, ...remaining];
480
+ }
481
+ routeSupportsInlineVision(routeName) {
482
+ const pools = this.routing[routeName];
483
+ if (!Array.isArray(pools)) {
484
+ return false;
485
+ }
486
+ for (const pool of pools) {
487
+ if (!Array.isArray(pool.targets)) {
488
+ continue;
489
+ }
490
+ for (const providerKey of pool.targets) {
491
+ try {
492
+ const profile = this.providerRegistry.get(providerKey);
493
+ if (profile.providerType === 'responses' || profile.providerType === 'gemini') {
494
+ return true;
495
+ }
496
+ }
497
+ catch {
498
+ // ignore unknown provider keys during capability probing
499
+ }
500
+ }
501
+ }
502
+ return false;
503
+ }
273
504
  sortByPriority(routeNames) {
274
505
  return [...routeNames].sort((a, b) => this.routeWeight(a) - this.routeWeight(b));
275
506
  }
@@ -277,25 +508,190 @@ export class VirtualRouterEngine {
277
508
  const idx = ROUTE_PRIORITY.indexOf(routeName);
278
509
  return idx >= 0 ? idx : ROUTE_PRIORITY.length;
279
510
  }
280
- buildHitReason(routeUsed, classification, features) {
511
+ routeHasForceFlag(routeName) {
512
+ const pools = this.routing[routeName];
513
+ if (!Array.isArray(pools)) {
514
+ return false;
515
+ }
516
+ return pools.some((pool) => pool.force);
517
+ }
518
+ routeHasTargets(pools) {
519
+ if (!Array.isArray(pools)) {
520
+ return false;
521
+ }
522
+ return pools.some((pool) => Array.isArray(pool.targets) && pool.targets.length > 0);
523
+ }
524
+ hasPrimaryPool(pools) {
525
+ if (!Array.isArray(pools)) {
526
+ return false;
527
+ }
528
+ return pools.some((pool) => !pool.backup && Array.isArray(pool.targets) && pool.targets.length > 0);
529
+ }
530
+ sortRoutePools(pools) {
531
+ if (!Array.isArray(pools)) {
532
+ return [];
533
+ }
534
+ return pools
535
+ .filter((pool) => Array.isArray(pool.targets) && pool.targets.length > 0)
536
+ .sort((a, b) => {
537
+ if (a.backup && !b.backup)
538
+ return 1;
539
+ if (!a.backup && b.backup)
540
+ return -1;
541
+ if (a.priority !== b.priority) {
542
+ return b.priority - a.priority;
543
+ }
544
+ return a.id.localeCompare(b.id);
545
+ });
546
+ }
547
+ flattenPoolTargets(pools) {
548
+ const flattened = [];
549
+ if (!Array.isArray(pools)) {
550
+ return flattened;
551
+ }
552
+ for (const pool of pools) {
553
+ if (!Array.isArray(pool.targets)) {
554
+ continue;
555
+ }
556
+ for (const target of pool.targets) {
557
+ if (typeof target === 'string' && target && !flattened.includes(target)) {
558
+ flattened.push(target);
559
+ }
560
+ }
561
+ }
562
+ return flattened;
563
+ }
564
+ buildHitReason(routeUsed, providerKey, classification, features) {
281
565
  const reasoning = classification.reasoning || '';
282
566
  const primary = reasoning.split('|')[0] || '';
283
- const lastToolName = features.lastAssistantToolName;
284
- if (routeUsed === 'tools') {
285
- if (lastToolName) {
286
- return primary ? `${primary}(${lastToolName})` : `tools(${lastToolName})`;
567
+ const commandDetail = features.lastAssistantToolLabel;
568
+ const base = (() => {
569
+ if (routeUsed === 'tools') {
570
+ return this.decorateWithDetail(primary || 'tools', primary, commandDetail);
571
+ }
572
+ if (routeUsed === 'thinking') {
573
+ return this.decorateWithDetail(primary || 'thinking', primary, commandDetail);
574
+ }
575
+ if (routeUsed === 'coding') {
576
+ return this.decorateWithDetail(primary || 'coding', primary, commandDetail);
577
+ }
578
+ if (routeUsed === 'web_search' || routeUsed === 'search') {
579
+ return this.decorateWithDetail(primary || routeUsed, primary, commandDetail);
287
580
  }
288
- return primary || 'tools';
581
+ if (routeUsed === DEFAULT_ROUTE && classification.fallback) {
582
+ return primary || 'fallback:default';
583
+ }
584
+ if (primary) {
585
+ return primary;
586
+ }
587
+ return routeUsed ? `route:${routeUsed}` : 'route:unknown';
588
+ })();
589
+ const contextDetail = this.describeContextUsage(providerKey, features.estimatedTokens);
590
+ if (contextDetail) {
591
+ return `${base}|context:${contextDetail}`;
592
+ }
593
+ return base;
594
+ }
595
+ decorateWithDetail(baseLabel, primaryReason, detail) {
596
+ const normalizedDetail = detail && detail.trim();
597
+ if (!normalizedDetail) {
598
+ return primaryReason || baseLabel;
289
599
  }
290
- if (routeUsed === 'thinking') {
291
- return primary || 'thinking';
600
+ if (primaryReason) {
601
+ return `${primaryReason}(${normalizedDetail})`;
292
602
  }
293
- if (routeUsed === DEFAULT_ROUTE && classification.fallback) {
294
- return primary || 'fallback:default';
603
+ return `${baseLabel}(${normalizedDetail})`;
604
+ }
605
+ formatVirtualRouterHit(routeName, poolId, providerKey, modelId, hitReason) {
606
+ try {
607
+ // 生成本地时间戳
608
+ const now = new Date();
609
+ const hours = String(now.getHours()).padStart(2, '0');
610
+ const minutes = String(now.getMinutes()).padStart(2, '0');
611
+ const seconds = String(now.getSeconds()).padStart(2, '0');
612
+ const timestamp = `${hours}:${minutes}:${seconds}`;
613
+ const prefixColor = '\x1b[38;5;208m';
614
+ const reset = '\x1b[0m';
615
+ const timeColor = '\x1b[90m'; // 灰色
616
+ const routeColor = this.resolveRouteColor(routeName);
617
+ const prefix = `${prefixColor}[virtual-router-hit]${reset}`;
618
+ const timeLabel = `${timeColor}${timestamp}${reset}`;
619
+ const { providerLabel, resolvedModel } = this.describeTargetProvider(providerKey, modelId);
620
+ const routeLabel = poolId ? `${routeName}/${poolId}` : routeName;
621
+ const targetLabel = `${routeLabel} -> ${providerLabel}${resolvedModel ? '.' + resolvedModel : ''}`;
622
+ const reasonLabel = hitReason ? ` reason=${hitReason}` : '';
623
+ return `${prefix} ${timeLabel} ${routeColor}${targetLabel}${reasonLabel}${reset}`;
295
624
  }
296
- if (primary) {
297
- return primary;
625
+ catch {
626
+ const now = new Date();
627
+ const timestamp = now.toLocaleTimeString('zh-CN', { hour12: false });
628
+ const routeLabel = poolId ? `${routeName}/${poolId}` : routeName;
629
+ return `[virtual-router-hit] ${timestamp} ${routeLabel} -> ${providerKey}${modelId ? '.' + modelId : ''}${hitReason ? ` reason=${hitReason}` : ''}`;
298
630
  }
299
- return routeUsed ? `route:${routeUsed}` : 'route:unknown';
631
+ }
632
+ resolveRouteColor(routeName) {
633
+ const reset = '\x1b[0m';
634
+ const map = {
635
+ tools: '\x1b[38;5;214m',
636
+ thinking: '\x1b[34m',
637
+ coding: '\x1b[35m',
638
+ longcontext: '\x1b[38;5;141m',
639
+ web_search: '\x1b[32m',
640
+ search: '\x1b[38;5;34m',
641
+ vision: '\x1b[38;5;207m',
642
+ background: '\x1b[90m'
643
+ };
644
+ return map[routeName] ?? '\x1b[36m';
645
+ }
646
+ describeContextUsage(providerKey, estimatedTokens) {
647
+ if (typeof estimatedTokens !== 'number' || !Number.isFinite(estimatedTokens) || estimatedTokens <= 0) {
648
+ return undefined;
649
+ }
650
+ let limit = DEFAULT_MODEL_CONTEXT_TOKENS;
651
+ try {
652
+ const profile = this.providerRegistry.get(providerKey);
653
+ if (profile?.maxContextTokens && Number.isFinite(profile.maxContextTokens)) {
654
+ limit = profile.maxContextTokens;
655
+ }
656
+ }
657
+ catch {
658
+ limit = DEFAULT_MODEL_CONTEXT_TOKENS;
659
+ }
660
+ if (!limit || limit <= 0) {
661
+ return undefined;
662
+ }
663
+ const ratio = estimatedTokens / limit;
664
+ const threshold = this.contextRouting?.warnRatio ?? 0.9;
665
+ if (ratio < threshold) {
666
+ return undefined;
667
+ }
668
+ return `${ratio.toFixed(2)}/${Math.round(limit)}`;
669
+ }
670
+ describeTargetProvider(providerKey, fallbackModelId) {
671
+ const parsed = this.parseProviderKey(providerKey);
672
+ if (!parsed) {
673
+ return { providerLabel: providerKey, resolvedModel: fallbackModelId };
674
+ }
675
+ const aliasLabel = parsed.keyAlias ? `${parsed.providerId}[${parsed.keyAlias}]` : parsed.providerId;
676
+ const resolvedModel = parsed.modelId || fallbackModelId;
677
+ return { providerLabel: aliasLabel, resolvedModel };
678
+ }
679
+ parseProviderKey(providerKey) {
680
+ const trimmed = typeof providerKey === 'string' ? providerKey.trim() : '';
681
+ if (!trimmed) {
682
+ return null;
683
+ }
684
+ const parts = trimmed.split('.');
685
+ if (parts.length < 2) {
686
+ return { providerId: trimmed };
687
+ }
688
+ if (parts.length === 2) {
689
+ return { providerId: parts[0], modelId: parts[1] };
690
+ }
691
+ return {
692
+ providerId: parts[0],
693
+ keyAlias: parts[1],
694
+ modelId: parts.slice(2).join('.')
695
+ };
300
696
  }
301
697
  }