@jsonstudio/rcc 0.90.814 → 0.90.876

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 (221) hide show
  1. package/README.md +8 -0
  2. package/configsamples/provider-default/ali-coding-plan/config.v2.json +76 -0
  3. package/configsamples/provider-default/antigravity/config.v2.json +142 -0
  4. package/configsamples/provider-default/ark-coding-plan/config.v2.json +64 -0
  5. package/configsamples/provider-default/crs/config.v2.json +54 -0
  6. package/configsamples/provider-default/deepseek-web/config.v2.json +56 -0
  7. package/configsamples/provider-default/gemini/config.v2.json +43 -0
  8. package/configsamples/provider-default/gemini-cli/config.v2.json +45 -0
  9. package/configsamples/provider-default/gemini-native/config.v2.json +208 -0
  10. package/configsamples/provider-default/glm/config.v2.json +33 -0
  11. package/configsamples/provider-default/glm-anthropic/config.v2.json +29 -0
  12. package/configsamples/provider-default/kimi/config.v2.json +25 -0
  13. package/configsamples/provider-default/lmstudio/config.v2.json +79 -0
  14. package/configsamples/provider-default/lmstudio-proxy/config.v2.json +78 -0
  15. package/configsamples/provider-default/manifest.json +31 -0
  16. package/configsamples/provider-default/meituan/config.v2.json +20 -0
  17. package/configsamples/provider-default/mimo/config.v2.json +26 -0
  18. package/configsamples/provider-default/modelscope/config.v2.json +81 -0
  19. package/configsamples/provider-default/my-openai/config.v2.json +20 -0
  20. package/configsamples/provider-default/nvidia/config.v2.json +32 -0
  21. package/configsamples/provider-default/opencode-zen-free/config.v2.json +56 -0
  22. package/configsamples/provider-default/openrouter/config.v2.json +210 -0
  23. package/configsamples/provider-default/qwen/config.v2.json +38 -0
  24. package/configsamples/provider-default/qwenchat/config.v2.json +53 -0
  25. package/configsamples/provider-default/tab/config.v2.json +26 -0
  26. package/configsamples/provider-default/tabglm/config.v2.json +77 -0
  27. package/dist/build-info.js +2 -2
  28. package/dist/cli/commands/config.d.ts +5 -0
  29. package/dist/cli/commands/config.js +369 -1
  30. package/dist/cli/commands/config.js.map +1 -1
  31. package/dist/cli/commands/examples.js +3 -0
  32. package/dist/cli/commands/examples.js.map +1 -1
  33. package/dist/cli/commands/init.js +25 -1
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/launcher-kernel.js +122 -46
  36. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  37. package/dist/cli/commands/start.js +60 -3
  38. package/dist/cli/commands/start.js.map +1 -1
  39. package/dist/cli/config/bundled-provider-pack.d.ts +20 -0
  40. package/dist/cli/config/bundled-provider-pack.js +146 -0
  41. package/dist/cli/config/bundled-provider-pack.js.map +1 -0
  42. package/dist/cli/register/status-config-commands.d.ts +2 -0
  43. package/dist/cli/register/status-config-commands.js.map +1 -1
  44. package/dist/cli.js +81 -28
  45. package/dist/cli.js.map +1 -1
  46. package/dist/debug/snapshot-store.js +2 -1
  47. package/dist/debug/snapshot-store.js.map +1 -1
  48. package/dist/index.js +23 -14
  49. package/dist/index.js.map +1 -1
  50. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +1 -1
  51. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
  52. package/dist/manager/quota/provider-quota-center.js +1 -1
  53. package/dist/manager/quota/provider-quota-center.js.map +1 -1
  54. package/dist/manager/storage/file-store.js +10 -0
  55. package/dist/manager/storage/file-store.js.map +1 -1
  56. package/dist/modules/llmswitch/bridge/snapshot-recorder-runtime.js +18 -1
  57. package/dist/modules/llmswitch/bridge/snapshot-recorder-runtime.js.map +1 -1
  58. package/dist/modules/llmswitch/bridge/snapshot-recorder.js +132 -51
  59. package/dist/modules/llmswitch/bridge/snapshot-recorder.js.map +1 -1
  60. package/dist/provider-sdk/provider-runtime-inference.js +2 -2
  61. package/dist/provider-sdk/provider-runtime-inference.js.map +1 -1
  62. package/dist/providers/auth/deepseek-account-token-acquirer.js +32 -3
  63. package/dist/providers/auth/deepseek-account-token-acquirer.js.map +1 -1
  64. package/dist/providers/core/api/provider-types.d.ts +11 -0
  65. package/dist/providers/core/config/service-profiles.js +1 -1
  66. package/dist/providers/core/runtime/deepseek-http-provider.d.ts +2 -0
  67. package/dist/providers/core/runtime/deepseek-http-provider.js +31 -1
  68. package/dist/providers/core/runtime/deepseek-http-provider.js.map +1 -1
  69. package/dist/providers/core/runtime/qwenchat-http-provider-helpers.d.ts +3 -2
  70. package/dist/providers/core/runtime/qwenchat-http-provider-helpers.js +513 -96
  71. package/dist/providers/core/runtime/qwenchat-http-provider-helpers.js.map +1 -1
  72. package/dist/providers/core/runtime/standard-tool-text-harvest.d.ts +8 -0
  73. package/dist/providers/core/runtime/standard-tool-text-harvest.js +24 -0
  74. package/dist/providers/core/runtime/standard-tool-text-harvest.js.map +1 -0
  75. package/dist/providers/core/runtime/standard-tool-text-request-transform.d.ts +4 -1
  76. package/dist/providers/core/runtime/standard-tool-text-request-transform.js +129 -3
  77. package/dist/providers/core/runtime/standard-tool-text-request-transform.js.map +1 -1
  78. package/dist/providers/core/utils/snapshot-writer.js +5 -2
  79. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  80. package/dist/providers/profile/provider-profile-loader.js +52 -1
  81. package/dist/providers/profile/provider-profile-loader.js.map +1 -1
  82. package/dist/providers/profile/provider-profile.d.ts +3 -0
  83. package/dist/server/handlers/handler-response-utils.js +1 -0
  84. package/dist/server/handlers/handler-response-utils.js.map +1 -1
  85. package/dist/server/handlers/images-handler.d.ts +9 -0
  86. package/dist/server/handlers/images-handler.js +258 -0
  87. package/dist/server/handlers/images-handler.js.map +1 -0
  88. package/dist/server/handlers/types.d.ts +7 -0
  89. package/dist/server/runtime/http-server/antigravity-startup-tasks.d.ts +3 -0
  90. package/dist/server/runtime/http-server/antigravity-startup-tasks.js +16 -0
  91. package/dist/server/runtime/http-server/antigravity-startup-tasks.js.map +1 -1
  92. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +3 -18
  93. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
  94. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.d.ts +7 -0
  95. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js +17 -0
  96. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js.map +1 -1
  97. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +50 -17
  98. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  99. package/dist/server/runtime/http-server/daemon-admin-routes.js +32 -2
  100. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  101. package/dist/server/runtime/http-server/executor/provider-response-converter.js +42 -13
  102. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
  103. package/dist/server/runtime/http-server/executor/provider-response-utils.js +41 -3
  104. package/dist/server/runtime/http-server/executor/provider-response-utils.js.map +1 -1
  105. package/dist/server/runtime/http-server/executor/usage-aggregator.js +7 -7
  106. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
  107. package/dist/server/runtime/http-server/executor/usage-logger.d.ts +9 -0
  108. package/dist/server/runtime/http-server/executor/usage-logger.js +35 -2
  109. package/dist/server/runtime/http-server/executor/usage-logger.js.map +1 -1
  110. package/dist/server/runtime/http-server/executor-metadata.js +12 -4
  111. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  112. package/dist/server/runtime/http-server/executor-pipeline.js +24 -15
  113. package/dist/server/runtime/http-server/executor-pipeline.js.map +1 -1
  114. package/dist/server/runtime/http-server/executor-provider.d.ts +6 -1
  115. package/dist/server/runtime/http-server/executor-provider.js +137 -5
  116. package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
  117. package/dist/server/runtime/http-server/http-server-bootstrap.js +6 -0
  118. package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
  119. package/dist/server/runtime/http-server/http-server-lifecycle.js +23 -15
  120. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
  121. package/dist/server/runtime/http-server/http-server-runtime-providers.js +14 -4
  122. package/dist/server/runtime/http-server/http-server-runtime-providers.js.map +1 -1
  123. package/dist/server/runtime/http-server/http-server-runtime-setup.js +83 -1
  124. package/dist/server/runtime/http-server/http-server-runtime-setup.js.map +1 -1
  125. package/dist/server/runtime/http-server/hub-shadow-compare.js +2 -41
  126. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
  127. package/dist/server/runtime/http-server/provider-routing-scope.d.ts +9 -0
  128. package/dist/server/runtime/http-server/provider-routing-scope.js +20 -0
  129. package/dist/server/runtime/http-server/provider-routing-scope.js.map +1 -0
  130. package/dist/server/runtime/http-server/provider-traffic-governor.d.ts +67 -0
  131. package/dist/server/runtime/http-server/provider-traffic-governor.js +467 -0
  132. package/dist/server/runtime/http-server/provider-traffic-governor.js.map +1 -0
  133. package/dist/server/runtime/http-server/request-executor.d.ts +8 -0
  134. package/dist/server/runtime/http-server/request-executor.js +446 -21
  135. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  136. package/dist/server/runtime/http-server/routes.js +13 -0
  137. package/dist/server/runtime/http-server/routes.js.map +1 -1
  138. package/dist/server/runtime/http-server/session-client-registry.js +30 -4
  139. package/dist/server/runtime/http-server/session-client-registry.js.map +1 -1
  140. package/dist/server/runtime/http-server/session-client-route-utils.d.ts +7 -0
  141. package/dist/server/runtime/http-server/session-client-route-utils.js +38 -0
  142. package/dist/server/runtime/http-server/session-client-route-utils.js.map +1 -1
  143. package/dist/server/runtime/http-server/session-client-routes.js +12 -2
  144. package/dist/server/runtime/http-server/session-client-routes.js.map +1 -1
  145. package/dist/server/utils/request-id-manager.js +42 -5
  146. package/dist/server/utils/request-id-manager.js.map +1 -1
  147. package/dist/server/utils/stage-logger.d.ts +1 -0
  148. package/dist/server/utils/stage-logger.js +27 -0
  149. package/dist/server/utils/stage-logger.js.map +1 -1
  150. package/dist/utils/errorsamples.js +3 -1
  151. package/dist/utils/errorsamples.js.map +1 -1
  152. package/dist/utils/sensitive-redaction.d.ts +1 -0
  153. package/dist/utils/sensitive-redaction.js +122 -0
  154. package/dist/utils/sensitive-redaction.js.map +1 -0
  155. package/dist/utils/snapshot-writer.js +162 -2
  156. package/dist/utils/snapshot-writer.js.map +1 -1
  157. package/docs/INSTALLATION_AND_QUICKSTART.md +14 -1
  158. package/docs/PORTS.md +12 -0
  159. package/docs/lmstudio-tool-calling.md +25 -0
  160. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.d.ts +3 -0
  161. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.js +62 -0
  162. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwenchat-web.json +47 -0
  163. package/node_modules/@jsonstudio/llms/dist/conversion/hub/node-support.js +5 -2
  164. package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/operation-table-runner.js +68 -7
  165. package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper-from-chat.js +138 -3
  166. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-chat-process-request-utils.js +24 -0
  167. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-chat-process-entry.js +7 -1
  168. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-request-stage.js +7 -0
  169. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.d.ts +24 -0
  170. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.js +203 -0
  171. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-route-and-outbound.js +17 -12
  172. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +11 -0
  173. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.js +82 -1
  174. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +47 -14
  175. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +43 -0
  176. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.js +222 -19
  177. package/node_modules/@jsonstudio/llms/dist/conversion/hub/policy/policy-engine.js +2 -2
  178. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process-pending-tool-sync.js +24 -7
  179. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +90 -1
  180. package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.d.ts +1 -0
  181. package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.js +252 -1
  182. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge/utils.js +5 -3
  183. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +44 -4
  184. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.d.ts +3 -1
  185. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.js +20 -23
  186. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +68 -30
  187. package/node_modules/@jsonstudio/llms/dist/conversion/snapshot-utils.js +194 -10
  188. package/node_modules/@jsonstudio/llms/dist/native/router_hotpath_napi.node +0 -0
  189. package/node_modules/@jsonstudio/llms/dist/quota/quota-state.js +2 -2
  190. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine/routing-state/store.js +35 -2
  191. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +9 -9
  192. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/sticky-session-store.js +104 -18
  193. package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +79 -32
  194. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +49 -0
  195. package/node_modules/@jsonstudio/llms/dist/servertool/pending-session.js +48 -2
  196. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +14 -1
  197. package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +1 -0
  198. package/node_modules/@jsonstudio/llms/package.json +1 -1
  199. package/node_modules/ajv/dist/compile/jtd/serialize.js +9 -2
  200. package/node_modules/ajv/dist/compile/jtd/serialize.js.map +1 -1
  201. package/node_modules/ajv/dist/core.d.ts +1 -0
  202. package/node_modules/ajv/dist/core.js.map +1 -1
  203. package/node_modules/ajv/dist/vocabularies/validation/pattern.js +13 -4
  204. package/node_modules/ajv/dist/vocabularies/validation/pattern.js.map +1 -1
  205. package/node_modules/ajv/lib/compile/jtd/serialize.ts +13 -2
  206. package/node_modules/ajv/lib/core.ts +1 -0
  207. package/node_modules/ajv/lib/vocabularies/validation/pattern.ts +15 -4
  208. package/node_modules/ajv/package.json +2 -1
  209. package/package.json +15 -10
  210. package/scripts/ci/repo-sanity.mjs +23 -2
  211. package/scripts/ci/secrets-check.mjs +48 -0
  212. package/scripts/ci/silent-failure-audit.mjs +192 -0
  213. package/scripts/mock-provider/run-regressions.mjs +1 -0
  214. package/scripts/monitor/memory-guard.mjs +207 -0
  215. package/scripts/pack-mode.mjs +32 -36
  216. package/scripts/publish-rcc.mjs +38 -60
  217. package/scripts/tests/apply-patch-loop.mjs +1 -0
  218. package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +2 -0
  219. package/scripts/tools-dev/responses-debug-client/src/index.ts +8 -3
  220. package/scripts/verify-e2e-toolcall.mjs +1 -0
  221. package/scripts/verify-install-e2e.mjs +2 -1
@@ -13,8 +13,9 @@ import { isPoolExhaustedPipelineError, mergeMetadataPreservingDefined, resolvePo
13
13
  import { resolveProviderRuntimeOrThrow } from './executor/provider-runtime-resolver.js';
14
14
  import { resolveProviderRequestContext } from './executor/provider-request-context.js';
15
15
  import { isServerToolEnabled } from './servertool-admin-state.js';
16
- import { registerRequestLogContext } from '../../utils/request-log-color.js';
16
+ import { registerRequestLogContext, resolveSessionLogColor } from '../../utils/request-log-color.js';
17
17
  import { STREAM_LOG_FINISH_REASON_KEY } from '../../utils/finish-reason.js';
18
+ import { createNoopProviderTrafficGovernor, getSharedProviderTrafficGovernor } from './provider-traffic-governor.js';
18
19
  const DEFAULT_MAX_PROVIDER_ATTEMPTS = 6;
19
20
  function readString(value) {
20
21
  if (typeof value !== 'string') {
@@ -163,16 +164,304 @@ function extractRetryErrorSnapshot(error) {
163
164
  reason
164
165
  };
165
166
  }
167
+ function readHubStageTop(metadata) {
168
+ if (!metadata || typeof metadata !== 'object') {
169
+ return undefined;
170
+ }
171
+ const rt = metadata.__rt && typeof metadata.__rt === 'object' && !Array.isArray(metadata.__rt)
172
+ ? metadata.__rt
173
+ : undefined;
174
+ const raw = rt?.hubStageTop;
175
+ if (!Array.isArray(raw) || raw.length === 0) {
176
+ return undefined;
177
+ }
178
+ const normalized = raw
179
+ .map((entry) => {
180
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
181
+ return null;
182
+ }
183
+ const record = entry;
184
+ const stage = typeof record.stage === 'string' ? record.stage.trim() : '';
185
+ const totalMs = typeof record.totalMs === 'number' && Number.isFinite(record.totalMs)
186
+ ? Math.max(0, Math.round(record.totalMs))
187
+ : undefined;
188
+ if (!stage || totalMs === undefined) {
189
+ return null;
190
+ }
191
+ const count = typeof record.count === 'number' && Number.isFinite(record.count)
192
+ ? Math.max(0, Math.floor(record.count))
193
+ : undefined;
194
+ const avgMs = typeof record.avgMs === 'number' && Number.isFinite(record.avgMs)
195
+ ? Math.max(0, Math.round(record.avgMs))
196
+ : undefined;
197
+ const maxMs = typeof record.maxMs === 'number' && Number.isFinite(record.maxMs)
198
+ ? Math.max(0, Math.round(record.maxMs))
199
+ : undefined;
200
+ return {
201
+ stage,
202
+ totalMs,
203
+ ...(count !== undefined ? { count } : {}),
204
+ ...(avgMs !== undefined ? { avgMs } : {}),
205
+ ...(maxMs !== undefined ? { maxMs } : {})
206
+ };
207
+ })
208
+ .filter((entry) => Boolean(entry));
209
+ return normalized.length ? normalized : undefined;
210
+ }
166
211
  function truncateReason(reason, maxLength = 220) {
167
212
  if (reason.length <= maxLength) {
168
213
  return reason;
169
214
  }
170
215
  return `${reason.slice(0, Math.max(0, maxLength - 1))}…`;
171
216
  }
217
+ function resolveTrafficRuntimeProfile(runtimeKey, handle, providerKey) {
218
+ const runtimeCandidate = handle.runtime;
219
+ if (runtimeCandidate && typeof runtimeCandidate === 'object') {
220
+ return runtimeCandidate;
221
+ }
222
+ const providerIdFallback = (() => {
223
+ if (typeof handle.providerId === 'string' && handle.providerId.trim()) {
224
+ return handle.providerId.trim();
225
+ }
226
+ if (typeof providerKey === 'string' && providerKey.includes('.')) {
227
+ const [head] = providerKey.split('.');
228
+ if (head && head.trim()) {
229
+ return head.trim();
230
+ }
231
+ }
232
+ return 'unknown';
233
+ })();
234
+ const providerTypeFallback = (typeof handle.providerType === 'string' && handle.providerType.trim()
235
+ ? handle.providerType.trim().toLowerCase()
236
+ : 'openai');
237
+ return {
238
+ runtimeKey,
239
+ providerId: providerIdFallback,
240
+ providerKey,
241
+ providerType: providerTypeFallback,
242
+ providerFamily: handle.providerFamily,
243
+ endpoint: '',
244
+ auth: {
245
+ type: 'apikey',
246
+ value: ''
247
+ }
248
+ };
249
+ }
250
+ function isRecord(value) {
251
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
252
+ }
253
+ function valueHasNonEmptyText(value) {
254
+ if (typeof value === 'string') {
255
+ return value.trim().length > 0;
256
+ }
257
+ if (Array.isArray(value)) {
258
+ return value.some((item) => valueHasNonEmptyText(item));
259
+ }
260
+ if (!isRecord(value)) {
261
+ return false;
262
+ }
263
+ return (valueHasNonEmptyText(value.text)
264
+ || valueHasNonEmptyText(value.output_text)
265
+ || valueHasNonEmptyText(value.content)
266
+ || valueHasNonEmptyText(value.reasoning_content)
267
+ || valueHasNonEmptyText(value.reasoning));
268
+ }
269
+ function extractTextFromResponsesOutputItem(item) {
270
+ if (!isRecord(item)) {
271
+ return '';
272
+ }
273
+ const itemType = readString(item.type)?.toLowerCase();
274
+ if (itemType === 'output_text' || itemType === 'text' || itemType === 'input_text') {
275
+ const direct = readString(item.text);
276
+ if (direct) {
277
+ return direct;
278
+ }
279
+ }
280
+ if (itemType === 'message') {
281
+ const content = Array.isArray(item.content) ? item.content : [];
282
+ const chunks = [];
283
+ for (const part of content) {
284
+ if (!isRecord(part)) {
285
+ continue;
286
+ }
287
+ const partType = readString(part.type)?.toLowerCase();
288
+ if (partType && partType !== 'output_text' && partType !== 'text' && partType !== 'input_text') {
289
+ continue;
290
+ }
291
+ const partText = readString(part.text);
292
+ if (partText) {
293
+ chunks.push(partText);
294
+ }
295
+ }
296
+ return chunks.join('');
297
+ }
298
+ return '';
299
+ }
300
+ function backfillResponsesOutputTextIfMissing(body) {
301
+ if (!isRecord(body)) {
302
+ return;
303
+ }
304
+ if (valueHasNonEmptyText(body.output_text)) {
305
+ return;
306
+ }
307
+ const outputItems = Array.isArray(body.output) ? body.output : [];
308
+ if (outputItems.length <= 0) {
309
+ return;
310
+ }
311
+ const text = outputItems
312
+ .map((item) => extractTextFromResponsesOutputItem(item))
313
+ .join('')
314
+ .trim();
315
+ if (!text) {
316
+ return;
317
+ }
318
+ body.output_text = text;
319
+ }
320
+ function emitVirtualRouterConcurrencyLog(args) {
321
+ const timestamp = (() => {
322
+ const now = new Date();
323
+ const hh = String(now.getHours()).padStart(2, '0');
324
+ const mm = String(now.getMinutes()).padStart(2, '0');
325
+ const ss = String(now.getSeconds()).padStart(2, '0');
326
+ return `${hh}:${mm}:${ss}`;
327
+ })();
328
+ const sessionLabel = args.sessionId ? ` sid=${args.sessionId}` : '';
329
+ const routeBase = args.routeName && args.routeName.trim() ? args.routeName.trim() : 'route';
330
+ const routeLabel = args.poolId && args.poolId.trim() ? `${routeBase}/${args.poolId.trim()}` : routeBase;
331
+ const providerLabel = args.providerKey && args.providerKey.trim() ? args.providerKey.trim() : 'unknown-provider';
332
+ const modelLabel = args.model && args.model.trim() ? `.${args.model.trim()}` : '';
333
+ const reasonLabel = args.reason && args.reason.trim() ? ` reason=${args.reason.trim()}` : '';
334
+ const prefixColor = '\x1b[38;5;208m';
335
+ const timeColor = '\x1b[90m';
336
+ const routeColor = resolveSessionLogColor(args.sessionId);
337
+ const white = '\x1b[97m';
338
+ const reset = '\x1b[0m';
339
+ const concurrencyLabel = `${white}[concurrency:${Math.max(0, Math.floor(args.activeInFlight))}/${Math.max(1, Math.floor(args.maxInFlight))}]${reset}`;
340
+ console.log(`${prefixColor}[virtual-router-hit]${reset} ${concurrencyLabel} ${timeColor}${timestamp}${reset}${sessionLabel} ${routeColor}${routeLabel} -> ${providerLabel}${modelLabel}${reasonLabel}${reset}`);
341
+ }
342
+ function hasNonEmptyToolCalls(value) {
343
+ if (!Array.isArray(value) || value.length <= 0) {
344
+ return false;
345
+ }
346
+ return value.some((item) => isRecord(item));
347
+ }
348
+ function hasOutputFunctionCalls(value) {
349
+ if (!Array.isArray(value) || value.length <= 0) {
350
+ return false;
351
+ }
352
+ return value.some((item) => {
353
+ if (!isRecord(item)) {
354
+ return false;
355
+ }
356
+ const itemType = readString(item.type)?.toLowerCase();
357
+ if (itemType === 'function_call' || itemType === 'function') {
358
+ return true;
359
+ }
360
+ if (hasNonEmptyToolCalls(item.tool_calls)) {
361
+ return true;
362
+ }
363
+ return false;
364
+ });
365
+ }
366
+ function containsToolRegistryMissingText(value) {
367
+ if (!valueHasNonEmptyText(value)) {
368
+ return false;
369
+ }
370
+ const text = String(value ?? '');
371
+ const pattern = /tool\s+[a-z0-9_.-]+\s+does\s+not\s+exist(?:s)?/ig;
372
+ let count = 0;
373
+ while (pattern.exec(text)) {
374
+ count += 1;
375
+ if (count >= 1) {
376
+ return true;
377
+ }
378
+ }
379
+ return false;
380
+ }
381
+ function detectRetryableEmptyAssistantResponse(body) {
382
+ if (!isRecord(body)) {
383
+ return null;
384
+ }
385
+ if (Object.prototype.hasOwnProperty.call(body, '__sse_responses')) {
386
+ return null;
387
+ }
388
+ const choices = Array.isArray(body.choices) ? body.choices : [];
389
+ if (choices.length > 0) {
390
+ const firstChoice = isRecord(choices[0]) ? choices[0] : undefined;
391
+ if (!firstChoice) {
392
+ return null;
393
+ }
394
+ const finishReason = readString(firstChoice.finish_reason)?.toLowerCase() ?? '';
395
+ const message = isRecord(firstChoice.message) ? firstChoice.message : undefined;
396
+ const hasToolCalls = hasNonEmptyToolCalls(message?.tool_calls);
397
+ const hasText = valueHasNonEmptyText(message?.content)
398
+ || valueHasNonEmptyText(message?.reasoning_content)
399
+ || valueHasNonEmptyText(message?.reasoning)
400
+ || valueHasNonEmptyText(firstChoice.content);
401
+ const combinedText = [
402
+ message?.content,
403
+ message?.reasoning_content,
404
+ message?.reasoning,
405
+ firstChoice.content
406
+ ]
407
+ .filter((item) => valueHasNonEmptyText(item))
408
+ .map((item) => String(item))
409
+ .join('\n');
410
+ if ((finishReason === 'stop' || finishReason === 'tool_calls' || !finishReason) && !hasToolCalls && !hasText) {
411
+ return {
412
+ reason: `finish_reason=${finishReason || 'unknown'} but assistant text/tool_calls are empty`,
413
+ marker: 'chat_empty_assistant'
414
+ };
415
+ }
416
+ if ((finishReason === 'stop' || finishReason === 'tool_calls' || !finishReason) && !hasToolCalls && containsToolRegistryMissingText(combinedText)) {
417
+ return {
418
+ reason: 'assistant emitted textual tool-not-found complaint without structured tool_calls',
419
+ marker: 'chat_textual_tool_registry_missing'
420
+ };
421
+ }
422
+ }
423
+ const status = readString(body.status)?.toLowerCase() ?? '';
424
+ if (status === 'completed' || status === 'stop') {
425
+ const requiredAction = isRecord(body.required_action) ? body.required_action : undefined;
426
+ const submitToolOutputs = requiredAction && isRecord(requiredAction.submit_tool_outputs)
427
+ ? requiredAction.submit_tool_outputs
428
+ : undefined;
429
+ const hasRequiredActionToolCalls = hasNonEmptyToolCalls(submitToolOutputs?.tool_calls);
430
+ const hasFunctionCalls = hasOutputFunctionCalls(body.output);
431
+ const hasText = valueHasNonEmptyText(body.output_text)
432
+ || valueHasNonEmptyText(body.output)
433
+ || valueHasNonEmptyText(body.reasoning);
434
+ if (!hasRequiredActionToolCalls && !hasFunctionCalls && !hasText) {
435
+ return {
436
+ reason: `responses status=${status} but output text/tool_calls are empty`,
437
+ marker: 'responses_empty_output'
438
+ };
439
+ }
440
+ if (!hasRequiredActionToolCalls &&
441
+ !hasFunctionCalls &&
442
+ containsToolRegistryMissingText(body.output_text)) {
443
+ return {
444
+ reason: 'responses completed with textual tool-not-found complaint but no function_call output',
445
+ marker: 'responses_textual_tool_registry_missing'
446
+ };
447
+ }
448
+ }
449
+ return null;
450
+ }
172
451
  export class HubRequestExecutor {
173
452
  deps;
453
+ trafficGovernor;
174
454
  constructor(deps) {
175
455
  this.deps = deps;
456
+ if (deps.trafficGovernor) {
457
+ this.trafficGovernor = deps.trafficGovernor;
458
+ return;
459
+ }
460
+ if (process.env.NODE_ENV === 'test') {
461
+ this.trafficGovernor = createNoopProviderTrafficGovernor();
462
+ return;
463
+ }
464
+ this.trafficGovernor = getSharedProviderTrafficGovernor();
176
465
  }
177
466
  logProviderRetrySwitch(args) {
178
467
  const providerLabel = args.providerKey || 'unknown-provider';
@@ -249,6 +538,27 @@ export class HubRequestExecutor {
249
538
  if (forcedRouteHint) {
250
539
  metadataForAttempt.routeHint = forcedRouteHint;
251
540
  }
541
+ const metadataRt = metadataForAttempt.__rt && typeof metadataForAttempt.__rt === 'object' && !Array.isArray(metadataForAttempt.__rt)
542
+ ? metadataForAttempt.__rt
543
+ : {};
544
+ metadataForAttempt.__rt = {
545
+ ...metadataRt,
546
+ disableVirtualRouterHitLog: true
547
+ };
548
+ // llmswitch Hub 仍有一条 legacy virtual-router-hit 调试日志(无 concurrency 信息)。
549
+ // 为保证控制台只保留一条统一格式(含 [concurrency:x/y])的命中日志,这里对
550
+ // metadata.logger 做最小化降噪:仅屏蔽 logVirtualRouterHit,不影响其他 logger 能力。
551
+ const loggerRecord = metadataForAttempt.logger &&
552
+ typeof metadataForAttempt.logger === 'object' &&
553
+ !Array.isArray(metadataForAttempt.logger)
554
+ ? metadataForAttempt.logger
555
+ : undefined;
556
+ if (loggerRecord && typeof loggerRecord.logVirtualRouterHit === 'function') {
557
+ metadataForAttempt.logger = {
558
+ ...loggerRecord,
559
+ logVirtualRouterHit: undefined
560
+ };
561
+ }
252
562
  const clientHeadersForAttempt = cloneClientHeaders(metadataForAttempt?.clientHeaders) || inboundClientHeaders;
253
563
  if (clientHeadersForAttempt) {
254
564
  metadataForAttempt.clientHeaders = clientHeadersForAttempt;
@@ -271,6 +581,13 @@ export class HubRequestExecutor {
271
581
  if (cooldownWaitMs &&
272
582
  attempt < maxAttempts &&
273
583
  poolCooldownWaitBudgetMs >= cooldownWaitMs) {
584
+ this.logStage(`${pipelineLabel}.completed`, providerRequestId, {
585
+ route: undefined,
586
+ target: undefined,
587
+ elapsedMs: Date.now() - hubStartedAtMs,
588
+ attempt,
589
+ recoverablePoolCooldown: true
590
+ });
274
591
  this.logStage('provider.route_pool_cooldown_wait', providerRequestId, {
275
592
  attempt,
276
593
  waitMs: cooldownWaitMs,
@@ -308,6 +625,9 @@ export class HubRequestExecutor {
308
625
  if (!initialRoutePool && Array.isArray(pipelineResult.routingDecision?.pool)) {
309
626
  initialRoutePool = [...pipelineResult.routingDecision.pool];
310
627
  }
628
+ const routePoolForAttempt = Array.isArray(pipelineResult.routingDecision?.pool)
629
+ ? pipelineResult.routingDecision.pool
630
+ : (initialRoutePool ?? []);
311
631
  const providerPayload = pipelineResult.providerPayload;
312
632
  const target = pipelineResult.target;
313
633
  if (!providerPayload || !target?.providerKey) {
@@ -392,11 +712,9 @@ export class HubRequestExecutor {
392
712
  throw error;
393
713
  }
394
714
  recordAttempt({ error: true });
715
+ const retryBackoffMs = await waitBeforeRetry(error, { attempt });
395
716
  const singleProviderPool = Boolean(initialRoutePool && initialRoutePool.length === 1 && initialRoutePool[0] === target.providerKey);
396
- if (singleProviderPool) {
397
- await waitBeforeRetry(error);
398
- }
399
- else if (target.providerKey) {
717
+ if (!singleProviderPool && target.providerKey) {
400
718
  excludedProviderKeys.add(target.providerKey);
401
719
  }
402
720
  const switchAction = singleProviderPool ? 'retry_same_provider' : 'exclude_and_reroute';
@@ -422,7 +740,8 @@ export class HubRequestExecutor {
422
740
  switchAction,
423
741
  ...(typeof retryError.statusCode === 'number' ? { statusCode: retryError.statusCode } : {}),
424
742
  ...(retryError.errorCode ? { errorCode: retryError.errorCode } : {}),
425
- ...(retryError.upstreamCode ? { upstreamCode: retryError.upstreamCode } : {})
743
+ ...(retryError.upstreamCode ? { upstreamCode: retryError.upstreamCode } : {}),
744
+ retryBackoffMs
426
745
  });
427
746
  continue;
428
747
  }
@@ -487,18 +806,65 @@ export class HubRequestExecutor {
487
806
  runtimeKey,
488
807
  attempt
489
808
  });
490
- const providerSendStartedAtMs = Date.now();
491
- this.logStage('provider.send.start', input.requestId, {
492
- providerKey: target.providerKey,
493
- runtimeKey,
494
- protocol: providerProtocol,
495
- providerType: handle.providerType,
496
- providerFamily: handle.providerFamily,
497
- model: providerModel,
498
- providerLabel,
499
- attempt
500
- });
809
+ let trafficPermit = null;
810
+ let providerSendStartedAtMs = 0;
501
811
  try {
812
+ this.logStage('provider.traffic.acquire.start', input.requestId, {
813
+ providerKey: target.providerKey,
814
+ runtimeKey,
815
+ attempt
816
+ });
817
+ const trafficAcquired = await this.trafficGovernor.acquire({
818
+ runtimeKey,
819
+ providerKey: target.providerKey,
820
+ requestId: input.requestId,
821
+ runtime: resolveTrafficRuntimeProfile(runtimeKey, handle, target.providerKey),
822
+ // If current route pool has alternatives, do not stall too long on quota/RPM gating.
823
+ // Switch provider after 10s wait so weighted pools can continue serving.
824
+ softWaitTimeoutMs: routePoolForAttempt.length > 1 ? 10_000 : undefined
825
+ });
826
+ trafficPermit = trafficAcquired.permit;
827
+ if (trafficAcquired.waitedMs > 0) {
828
+ this.logStage('provider.traffic.acquire.wait', input.requestId, {
829
+ providerKey: target.providerKey,
830
+ runtimeKey,
831
+ waitedMs: trafficAcquired.waitedMs,
832
+ attempt
833
+ });
834
+ }
835
+ this.logStage('provider.traffic.acquire.completed', input.requestId, {
836
+ providerKey: target.providerKey,
837
+ runtimeKey,
838
+ maxInFlight: trafficAcquired.policy.concurrency.maxInFlight,
839
+ requestsPerMinute: trafficAcquired.policy.rpm.requestsPerMinute,
840
+ activeInFlight: trafficAcquired.activeInFlight,
841
+ rpmInWindow: trafficAcquired.rpmInWindow,
842
+ attempt
843
+ });
844
+ const routingDecisionRecord = pipelineResult.routingDecision && typeof pipelineResult.routingDecision === 'object'
845
+ ? pipelineResult.routingDecision
846
+ : undefined;
847
+ emitVirtualRouterConcurrencyLog({
848
+ sessionId: readString(mergedMetadata.sessionId) ?? readString(mergedMetadata.conversationId),
849
+ routeName: pipelineResult.routingDecision?.routeName,
850
+ poolId: readString(routingDecisionRecord?.poolId),
851
+ providerKey: target.providerKey,
852
+ model: providerModel,
853
+ reason: readString(routingDecisionRecord?.reasoning),
854
+ activeInFlight: trafficAcquired.activeInFlight,
855
+ maxInFlight: trafficAcquired.policy.concurrency.maxInFlight
856
+ });
857
+ providerSendStartedAtMs = Date.now();
858
+ this.logStage('provider.send.start', input.requestId, {
859
+ providerKey: target.providerKey,
860
+ runtimeKey,
861
+ protocol: providerProtocol,
862
+ providerType: handle.providerType,
863
+ providerFamily: handle.providerFamily,
864
+ model: providerModel,
865
+ providerLabel,
866
+ attempt
867
+ });
502
868
  const providerResponse = await handle.instance.processIncoming(providerPayload);
503
869
  const responseStatus = extractResponseStatus(providerResponse);
504
870
  this.logStage('provider.send.completed', input.requestId, {
@@ -582,6 +948,9 @@ export class HubRequestExecutor {
582
948
  const convertedBodyRecord = converted.body && typeof converted.body === 'object'
583
949
  ? converted.body
584
950
  : undefined;
951
+ if (convertedBodyRecord) {
952
+ backfillResponsesOutputTextIfMissing(convertedBodyRecord);
953
+ }
585
954
  const finishReason = convertedBodyRecord && typeof convertedBodyRecord[STREAM_LOG_FINISH_REASON_KEY] === 'string'
586
955
  ? String(convertedBodyRecord[STREAM_LOG_FINISH_REASON_KEY])
587
956
  : undefined;
@@ -683,6 +1052,22 @@ export class HubRequestExecutor {
683
1052
  convertedStatus,
684
1053
  attempt
685
1054
  });
1055
+ const emptyAssistantSignal = detectRetryableEmptyAssistantResponse(converted.body);
1056
+ if (emptyAssistantSignal) {
1057
+ const bodyForError = converted.body;
1058
+ const errorToThrow = new Error(`Upstream returned empty assistant payload: ${emptyAssistantSignal.reason}`);
1059
+ errorToThrow.statusCode = 502;
1060
+ errorToThrow.status = 502;
1061
+ errorToThrow.code = 'EMPTY_ASSISTANT_RESPONSE';
1062
+ errorToThrow.response = { data: bodyForError };
1063
+ this.logStage('provider.empty_assistant_retry', input.requestId, {
1064
+ providerKey: target.providerKey,
1065
+ marker: emptyAssistantSignal.marker,
1066
+ reason: emptyAssistantSignal.reason,
1067
+ attempt
1068
+ });
1069
+ throw errorToThrow;
1070
+ }
686
1071
  this.logStage('provider.usage_extract.start', input.requestId, {
687
1072
  providerKey: target.providerKey,
688
1073
  source: 'converted_response',
@@ -711,12 +1096,14 @@ export class HubRequestExecutor {
711
1096
  attempt
712
1097
  });
713
1098
  recordAttempt({ usage: aggregatedUsage, error: false });
1099
+ const metadataHubStageTop = readHubStageTop(mergedMetadata);
714
1100
  return {
715
1101
  ...converted,
716
1102
  usageLogInfo: {
717
1103
  providerKey: target.providerKey,
718
1104
  model: providerModel,
719
1105
  usage: aggregatedUsage,
1106
+ hubStageTop: metadataHubStageTop,
720
1107
  requestStartedAtMs: requestStartedAt,
721
1108
  timingRequestIds: Array.from(new Set([providerRequestId, input.requestId].filter((value) => Boolean(value)))),
722
1109
  sessionId: mergedMetadata.sessionId,
@@ -839,13 +1226,11 @@ export class HubRequestExecutor {
839
1226
  }
840
1227
  // Record this failed provider attempt even if the overall request succeeds later via failover.
841
1228
  recordAttempt({ error: true });
1229
+ const retryBackoffMs = await waitBeforeRetry(error, { attempt });
842
1230
  const singleProviderPool = Boolean(initialRoutePool && initialRoutePool.length === 1 && initialRoutePool[0] === target.providerKey);
843
1231
  if (promptTooLong && target.providerKey) {
844
1232
  excludedProviderKeys.add(target.providerKey);
845
1233
  }
846
- else if (singleProviderPool) {
847
- await waitBeforeRetry(error);
848
- }
849
1234
  if (!promptTooLong && !singleProviderPool && target.providerKey) {
850
1235
  const is429 = status === 429;
851
1236
  if (isAntigravityProviderKey(target.providerKey) && (isVerify || is429)) {
@@ -894,10 +1279,49 @@ export class HubRequestExecutor {
894
1279
  ...(typeof retryError.statusCode === 'number' ? { statusCode: retryError.statusCode } : {}),
895
1280
  ...(retryError.errorCode ? { errorCode: retryError.errorCode } : {}),
896
1281
  ...(retryError.upstreamCode ? { upstreamCode: retryError.upstreamCode } : {}),
1282
+ retryBackoffMs,
897
1283
  ...(promptTooLong ? { contextOverflowRetries, maxContextOverflowRetries: MAX_CONTEXT_OVERFLOW_RETRIES } : {})
898
1284
  });
899
1285
  continue;
900
1286
  }
1287
+ finally {
1288
+ if (trafficPermit) {
1289
+ const releaseStartedAtMs = Date.now();
1290
+ this.logStage('provider.traffic.release.start', input.requestId, {
1291
+ providerKey: target.providerKey,
1292
+ runtimeKey,
1293
+ leaseId: trafficPermit.leaseId,
1294
+ attempt
1295
+ });
1296
+ try {
1297
+ const released = await this.trafficGovernor.release(trafficPermit);
1298
+ this.logStage('provider.traffic.release.completed', input.requestId, {
1299
+ providerKey: target.providerKey,
1300
+ runtimeKey,
1301
+ leaseId: trafficPermit.leaseId,
1302
+ released: released.released,
1303
+ activeInFlight: released.activeInFlight,
1304
+ elapsedMs: Date.now() - releaseStartedAtMs,
1305
+ attempt
1306
+ });
1307
+ }
1308
+ catch (releaseError) {
1309
+ this.logStage('provider.traffic.release.error', input.requestId, {
1310
+ providerKey: target.providerKey,
1311
+ runtimeKey,
1312
+ leaseId: trafficPermit.leaseId,
1313
+ message: releaseError instanceof Error
1314
+ ? releaseError.message
1315
+ : String(releaseError ?? 'Unknown release error'),
1316
+ elapsedMs: Date.now() - releaseStartedAtMs,
1317
+ attempt
1318
+ });
1319
+ }
1320
+ finally {
1321
+ trafficPermit = null;
1322
+ }
1323
+ }
1324
+ }
901
1325
  }
902
1326
  throw lastError ?? new Error('Provider execution failed without response');
903
1327
  }
@@ -924,7 +1348,8 @@ export class HubRequestExecutor {
924
1348
  export const __requestExecutorTestables = {
925
1349
  readString,
926
1350
  extractRetryErrorSnapshot,
927
- truncateReason
1351
+ truncateReason,
1352
+ detectRetryableEmptyAssistantResponse
928
1353
  };
929
1354
  export function createRequestExecutor(deps) {
930
1355
  return new HubRequestExecutor(deps);