@jsonstudio/rcc 0.89.1803 → 0.89.1959

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 (283) hide show
  1. package/configsamples/config.json +19 -0
  2. package/configsamples/provider/deepseek/config.v1.json +59 -0
  3. package/dist/build-info.js +2 -2
  4. package/dist/cli/commands/claude.d.ts +4 -0
  5. package/dist/cli/commands/claude.js +56 -0
  6. package/dist/cli/commands/claude.js.map +1 -0
  7. package/dist/cli/commands/clock-admin.d.ts +20 -0
  8. package/dist/cli/commands/clock-admin.js +234 -0
  9. package/dist/cli/commands/clock-admin.js.map +1 -0
  10. package/dist/cli/commands/code.d.ts +0 -42
  11. package/dist/cli/commands/code.js +4 -414
  12. package/dist/cli/commands/code.js.map +1 -1
  13. package/dist/cli/commands/codex.d.ts +4 -0
  14. package/dist/cli/commands/codex.js +43 -0
  15. package/dist/cli/commands/codex.js.map +1 -0
  16. package/dist/cli/commands/examples.js +13 -16
  17. package/dist/cli/commands/examples.js.map +1 -1
  18. package/dist/cli/commands/init/basic.d.ts +40 -0
  19. package/dist/cli/commands/init/basic.js +482 -0
  20. package/dist/cli/commands/init/basic.js.map +1 -0
  21. package/dist/cli/commands/init/camoufox.d.ts +7 -0
  22. package/dist/cli/commands/init/camoufox.js +59 -0
  23. package/dist/cli/commands/init/camoufox.js.map +1 -0
  24. package/dist/cli/commands/init/interactive.d.ts +18 -0
  25. package/dist/cli/commands/init/interactive.js +223 -0
  26. package/dist/cli/commands/init/interactive.js.map +1 -0
  27. package/dist/cli/commands/init/shared.d.ts +66 -0
  28. package/dist/cli/commands/init/shared.js +9 -0
  29. package/dist/cli/commands/init/shared.js.map +1 -0
  30. package/dist/cli/commands/init/workflows.d.ts +29 -0
  31. package/dist/cli/commands/init/workflows.js +341 -0
  32. package/dist/cli/commands/init/workflows.js.map +1 -0
  33. package/dist/cli/commands/init.d.ts +2 -26
  34. package/dist/cli/commands/init.js +220 -53
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/launcher-kernel.d.ts +78 -0
  37. package/dist/cli/commands/launcher-kernel.js +1194 -0
  38. package/dist/cli/commands/launcher-kernel.js.map +1 -0
  39. package/dist/cli/commands/start.js +27 -1
  40. package/dist/cli/commands/start.js.map +1 -1
  41. package/dist/cli/commands/status.d.ts +2 -0
  42. package/dist/cli/commands/status.js +24 -1
  43. package/dist/cli/commands/status.js.map +1 -1
  44. package/dist/cli/commands/stop.d.ts +1 -0
  45. package/dist/cli/commands/stop.js +201 -4
  46. package/dist/cli/commands/stop.js.map +1 -1
  47. package/dist/cli/commands/tmux-inject.d.ts +20 -0
  48. package/dist/cli/commands/tmux-inject.js +212 -0
  49. package/dist/cli/commands/tmux-inject.js.map +1 -0
  50. package/dist/cli/config/init-provider-catalog.js +34 -0
  51. package/dist/cli/config/init-provider-catalog.js.map +1 -1
  52. package/dist/cli/register/claude-command.d.ts +3 -0
  53. package/dist/cli/register/claude-command.js +5 -0
  54. package/dist/cli/register/claude-command.js.map +1 -0
  55. package/dist/cli/register/clock-admin-command.d.ts +3 -0
  56. package/dist/cli/register/clock-admin-command.js +5 -0
  57. package/dist/cli/register/clock-admin-command.js.map +1 -0
  58. package/dist/cli/register/codex-command.d.ts +3 -0
  59. package/dist/cli/register/codex-command.js +5 -0
  60. package/dist/cli/register/codex-command.js.map +1 -0
  61. package/dist/cli/register/status-config-commands.d.ts +2 -0
  62. package/dist/cli/register/status-config-commands.js.map +1 -1
  63. package/dist/cli/register/tmux-inject-command.d.ts +3 -0
  64. package/dist/cli/register/tmux-inject-command.js +5 -0
  65. package/dist/cli/register/tmux-inject-command.js.map +1 -0
  66. package/dist/cli/server/port-utils.d.ts +3 -2
  67. package/dist/cli/server/port-utils.js +171 -32
  68. package/dist/cli/server/port-utils.js.map +1 -1
  69. package/dist/cli.js +45 -6
  70. package/dist/cli.js.map +1 -1
  71. package/dist/client/gemini/gemini-protocol-client.js +56 -5
  72. package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
  73. package/dist/commands/token-daemon.js +59 -7
  74. package/dist/commands/token-daemon.js.map +1 -1
  75. package/dist/commands/validate.js +87 -15
  76. package/dist/commands/validate.js.map +1 -1
  77. package/dist/config/routecodex-config-loader.js +31 -2
  78. package/dist/config/routecodex-config-loader.js.map +1 -1
  79. package/dist/docs/daemon-admin-ui.html +948 -74
  80. package/dist/index.d.ts +1 -0
  81. package/dist/index.js +325 -37
  82. package/dist/index.js.map +1 -1
  83. package/dist/manager/quota/provider-quota-center.js +8 -14
  84. package/dist/manager/quota/provider-quota-center.js.map +1 -1
  85. package/dist/modules/llmswitch/bridge.d.ts +39 -0
  86. package/dist/modules/llmswitch/bridge.js +169 -0
  87. package/dist/modules/llmswitch/bridge.js.map +1 -1
  88. package/dist/modules/pipeline/utils/colored-logger.js +1 -1
  89. package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
  90. package/dist/providers/auth/deepseek-account-auth.d.ts +39 -0
  91. package/dist/providers/auth/deepseek-account-auth.js +329 -0
  92. package/dist/providers/auth/deepseek-account-auth.js.map +1 -0
  93. package/dist/providers/auth/deepseek-account-token-acquirer.d.ts +15 -0
  94. package/dist/providers/auth/deepseek-account-token-acquirer.js +644 -0
  95. package/dist/providers/auth/deepseek-account-token-acquirer.js.map +1 -0
  96. package/dist/providers/auth/oauth-lifecycle.js +26 -4
  97. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  98. package/dist/providers/auth/oauth-repair-cooldown.d.ts +5 -0
  99. package/dist/providers/auth/oauth-repair-cooldown.js +39 -0
  100. package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
  101. package/dist/providers/auth/token-scanner/index.d.ts +6 -0
  102. package/dist/providers/auth/token-scanner/index.js +53 -0
  103. package/dist/providers/auth/token-scanner/index.js.map +1 -1
  104. package/dist/providers/core/api/provider-config.d.ts +17 -2
  105. package/dist/providers/core/api/provider-types.d.ts +6 -0
  106. package/dist/providers/core/api/provider-types.js.map +1 -1
  107. package/dist/providers/core/config/camoufox-launcher.d.ts +7 -0
  108. package/dist/providers/core/config/camoufox-launcher.js +68 -21
  109. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  110. package/dist/providers/core/config/service-profiles.js +19 -0
  111. package/dist/providers/core/config/service-profiles.js.map +1 -1
  112. package/dist/providers/core/contracts/deepseek-provider-contract.d.ts +34 -0
  113. package/dist/providers/core/contracts/deepseek-provider-contract.js +100 -0
  114. package/dist/providers/core/contracts/deepseek-provider-contract.js.map +1 -0
  115. package/dist/providers/core/runtime/anthropic-http-provider.d.ts +0 -5
  116. package/dist/providers/core/runtime/anthropic-http-provider.js +0 -26
  117. package/dist/providers/core/runtime/anthropic-http-provider.js.map +1 -1
  118. package/dist/providers/core/runtime/deepseek-http-provider.d.ts +35 -0
  119. package/dist/providers/core/runtime/deepseek-http-provider.js +373 -0
  120. package/dist/providers/core/runtime/deepseek-http-provider.js.map +1 -0
  121. package/dist/providers/core/runtime/deepseek-session-pow.d.ts +55 -0
  122. package/dist/providers/core/runtime/deepseek-session-pow.js +422 -0
  123. package/dist/providers/core/runtime/deepseek-session-pow.js.map +1 -0
  124. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +0 -3
  125. package/dist/providers/core/runtime/gemini-cli-http-provider.js +0 -72
  126. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  127. package/dist/providers/core/runtime/gemini-http-provider.d.ts +1 -7
  128. package/dist/providers/core/runtime/gemini-http-provider.js +3 -110
  129. package/dist/providers/core/runtime/gemini-http-provider.js.map +1 -1
  130. package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
  131. package/dist/providers/core/runtime/http-request-executor.js +4 -0
  132. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  133. package/dist/providers/core/runtime/http-transport-provider.d.ts +10 -4
  134. package/dist/providers/core/runtime/http-transport-provider.js +308 -82
  135. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  136. package/dist/providers/core/runtime/iflow-http-provider.d.ts +0 -4
  137. package/dist/providers/core/runtime/iflow-http-provider.js +0 -28
  138. package/dist/providers/core/runtime/iflow-http-provider.js.map +1 -1
  139. package/dist/providers/core/runtime/provider-factory.d.ts +5 -0
  140. package/dist/providers/core/runtime/provider-factory.js +59 -6
  141. package/dist/providers/core/runtime/provider-factory.js.map +1 -1
  142. package/dist/providers/core/runtime/responses-provider.d.ts +0 -2
  143. package/dist/providers/core/runtime/responses-provider.js +0 -11
  144. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  145. package/dist/providers/core/strategies/oauth-device-flow.js +16 -1
  146. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  147. package/dist/providers/core/utils/provider-type-utils.js +2 -1
  148. package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
  149. package/dist/providers/profile/families/anthropic-profile.d.ts +2 -0
  150. package/dist/providers/profile/families/anthropic-profile.js +32 -0
  151. package/dist/providers/profile/families/anthropic-profile.js.map +1 -0
  152. package/dist/providers/profile/families/antigravity-profile.d.ts +2 -0
  153. package/dist/providers/profile/families/antigravity-profile.js +109 -0
  154. package/dist/providers/profile/families/antigravity-profile.js.map +1 -0
  155. package/dist/providers/profile/families/glm-profile.d.ts +2 -0
  156. package/dist/providers/profile/families/glm-profile.js +48 -0
  157. package/dist/providers/profile/families/glm-profile.js.map +1 -0
  158. package/dist/providers/profile/families/iflow-profile.d.ts +2 -0
  159. package/dist/providers/profile/families/iflow-profile.js +232 -0
  160. package/dist/providers/profile/families/iflow-profile.js.map +1 -0
  161. package/dist/providers/profile/families/qwen-profile.d.ts +2 -0
  162. package/dist/providers/profile/families/qwen-profile.js +14 -0
  163. package/dist/providers/profile/families/qwen-profile.js.map +1 -0
  164. package/dist/providers/profile/families/responses-profile.d.ts +2 -0
  165. package/dist/providers/profile/families/responses-profile.js +28 -0
  166. package/dist/providers/profile/families/responses-profile.js.map +1 -0
  167. package/dist/providers/profile/profile-contracts.d.ts +74 -0
  168. package/dist/providers/profile/profile-contracts.js +2 -0
  169. package/dist/providers/profile/profile-contracts.js.map +1 -0
  170. package/dist/providers/profile/profile-registry.d.ts +3 -0
  171. package/dist/providers/profile/profile-registry.js +40 -0
  172. package/dist/providers/profile/profile-registry.js.map +1 -0
  173. package/dist/providers/profile/provider-directory.d.ts +2 -0
  174. package/dist/providers/profile/provider-directory.js +55 -0
  175. package/dist/providers/profile/provider-directory.js.map +1 -0
  176. package/dist/providers/profile/provider-profile-loader.js +43 -3
  177. package/dist/providers/profile/provider-profile-loader.js.map +1 -1
  178. package/dist/providers/profile/provider-profile.d.ts +8 -0
  179. package/dist/scripts/deepseek/pow-solver.mjs +146 -0
  180. package/dist/scripts/deepseek/sha3_wasm_bg.7b9ca65ddd.wasm +0 -0
  181. package/dist/server/handlers/config-admin-handler.js +27 -0
  182. package/dist/server/handlers/config-admin-handler.js.map +1 -1
  183. package/dist/server/runtime/http-server/clock-client-registry.d.ts +113 -0
  184. package/dist/server/runtime/http-server/clock-client-registry.js +592 -0
  185. package/dist/server/runtime/http-server/clock-client-registry.js.map +1 -0
  186. package/dist/server/runtime/http-server/clock-client-routes.d.ts +2 -0
  187. package/dist/server/runtime/http-server/clock-client-routes.js +481 -0
  188. package/dist/server/runtime/http-server/clock-client-routes.js.map +1 -0
  189. package/dist/server/runtime/http-server/clock-daemon-inject-config.d.ts +1 -0
  190. package/dist/server/runtime/http-server/clock-daemon-inject-config.js +11 -0
  191. package/dist/server/runtime/http-server/clock-daemon-inject-config.js.map +1 -0
  192. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +3 -3
  193. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
  194. package/dist/server/runtime/http-server/daemon-admin/auth-session.d.ts +1 -0
  195. package/dist/server/runtime/http-server/daemon-admin/auth-session.js +18 -2
  196. package/dist/server/runtime/http-server/daemon-admin/auth-session.js.map +1 -1
  197. package/dist/server/runtime/http-server/daemon-admin/control-handler.js +2 -15
  198. package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
  199. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +65 -7
  200. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  201. package/dist/server/runtime/http-server/executor-metadata.js +37 -1
  202. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  203. package/dist/server/runtime/http-server/executor-provider.js +55 -0
  204. package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
  205. package/dist/server/runtime/http-server/executor-response.js +49 -1
  206. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  207. package/dist/server/runtime/http-server/index.d.ts +10 -0
  208. package/dist/server/runtime/http-server/index.js +534 -9
  209. package/dist/server/runtime/http-server/index.js.map +1 -1
  210. package/dist/server/runtime/http-server/managed-process-probe.d.ts +6 -0
  211. package/dist/server/runtime/http-server/managed-process-probe.js +294 -0
  212. package/dist/server/runtime/http-server/managed-process-probe.js.map +1 -0
  213. package/dist/server/runtime/http-server/middleware.js +16 -1
  214. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  215. package/dist/server/runtime/http-server/provider-utils.js +6 -2
  216. package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
  217. package/dist/server/runtime/http-server/request-executor.d.ts +1 -0
  218. package/dist/server/runtime/http-server/request-executor.js +360 -35
  219. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  220. package/dist/server/runtime/http-server/routes.js +95 -3
  221. package/dist/server/runtime/http-server/routes.js.map +1 -1
  222. package/dist/server/runtime/http-server/stats-manager.d.ts +10 -0
  223. package/dist/server/runtime/http-server/stats-manager.js +119 -16
  224. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  225. package/dist/server/runtime/http-server/tmux-session-probe.d.ts +3 -0
  226. package/dist/server/runtime/http-server/tmux-session-probe.js +101 -0
  227. package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -0
  228. package/dist/server/utils/stage-logger.js +21 -5
  229. package/dist/server/utils/stage-logger.js.map +1 -1
  230. package/dist/token-daemon/index.js +59 -10
  231. package/dist/token-daemon/index.js.map +1 -1
  232. package/dist/token-daemon/server-utils.d.ts +1 -0
  233. package/dist/token-daemon/server-utils.js +4 -1
  234. package/dist/token-daemon/server-utils.js.map +1 -1
  235. package/dist/token-daemon/token-daemon.js +38 -4
  236. package/dist/token-daemon/token-daemon.js.map +1 -1
  237. package/dist/token-daemon/token-types.d.ts +1 -1
  238. package/dist/token-daemon/token-types.js +2 -1
  239. package/dist/token-daemon/token-types.js.map +1 -1
  240. package/dist/token-daemon/token-utils.js +5 -2
  241. package/dist/token-daemon/token-utils.js.map +1 -1
  242. package/dist/utils/clock-client-token.d.ts +3 -0
  243. package/dist/utils/clock-client-token.js +54 -0
  244. package/dist/utils/clock-client-token.js.map +1 -0
  245. package/dist/utils/managed-server-pids.d.ts +25 -0
  246. package/dist/utils/managed-server-pids.js +176 -0
  247. package/dist/utils/managed-server-pids.js.map +1 -0
  248. package/dist/utils/process-lifecycle-logger.d.ts +8 -0
  249. package/dist/utils/process-lifecycle-logger.js +151 -0
  250. package/dist/utils/process-lifecycle-logger.js.map +1 -0
  251. package/dist/utils/runtime-exit-forensics.d.ts +30 -0
  252. package/dist/utils/runtime-exit-forensics.js +101 -0
  253. package/dist/utils/runtime-exit-forensics.js.map +1 -0
  254. package/dist/utils/shutdown-caller-context.d.ts +22 -0
  255. package/dist/utils/shutdown-caller-context.js +25 -0
  256. package/dist/utils/shutdown-caller-context.js.map +1 -0
  257. package/docs/PROVIDERS_BUILTIN.md +8 -0
  258. package/docs/PROVIDER_TYPES.md +3 -1
  259. package/docs/SERVERTOOL_PRE_COMMAND_HOOKS.md +85 -0
  260. package/docs/clock-client-daemon-design.md +343 -0
  261. package/docs/daemon-admin-ui.html +948 -74
  262. package/docs/providers/deepseek-web-provider-design.md +192 -0
  263. package/docs/routing-instructions.md +4 -1
  264. package/docs/stop-message-auto.md +4 -3
  265. package/docs/v2-architecture/PROVIDER-V2-CHANGESET-RELEASE-CHECKLIST.md +80 -0
  266. package/docs/v2-architecture/PROVIDER-V2-LAYERING-ADR-DRAFT.md +225 -0
  267. package/docs/v2-architecture/PROVIDER-V2-MIGRATION-MATRIX-DRAFT.md +88 -0
  268. package/docs/v2-architecture/PROVIDER-V2-PHASED-MIGRATION-ROLLBACK-DRAFT.md +164 -0
  269. package/docs/v2-architecture/PROVIDER-V2-PROFILE-API-REGISTRY-DRAFT.md +201 -0
  270. package/docs/v2-architecture/PROVIDER-V2-PROFILE-GEMINI-DRAFT.md +56 -0
  271. package/docs/v2-architecture/PROVIDER-V2-REFACTOR-OVERVIEW-DRAFT.md +102 -0
  272. package/docs/v2-architecture/PROVIDER-V2-VERIFICATION-MATRIX-DRAFT.md +163 -0
  273. package/package.json +10 -9
  274. package/scripts/copy-compat-assets.mjs +18 -0
  275. package/scripts/copy-modules-config.mjs +1 -0
  276. package/scripts/deepseek/pow-solver.mjs +146 -0
  277. package/scripts/deepseek/sha3_wasm_bg.7b9ca65ddd.wasm +0 -0
  278. package/scripts/ensure-cli-executable.mjs +64 -0
  279. package/scripts/install-global.sh +5 -2
  280. package/scripts/install.sh +1 -1
  281. package/scripts/monitor/daemon-kill-watch.mjs +184 -0
  282. package/scripts/monitor/port-kill-watch.sh +74 -0
  283. package/scripts/quick-install.sh +1 -1
@@ -8,12 +8,15 @@
8
8
  * - 保持API兼容性
9
9
  */
10
10
  import express, {} from 'express';
11
+ import * as fs from 'node:fs/promises';
12
+ import path from 'node:path';
11
13
  import { ErrorHandlingCenter } from 'rcc-errorhandling';
12
14
  import { ProviderFactory } from '../../../providers/core/runtime/provider-factory.js';
13
15
  import { PipelineDebugLogger as PipelineDebugLoggerImpl } from '../../../modules/pipeline/utils/debug-logger.js';
14
16
  import { attachProviderRuntimeMetadata } from '../../../providers/core/runtime/provider-runtime-metadata.js';
15
17
  import { preloadAntigravityAliasUserAgents, primeAntigravityUserAgentVersion } from '../../../providers/auth/antigravity-user-agent.js';
16
18
  import { getAntigravityWarmupBlacklistDurationMs, isAntigravityWarmupEnabled, warmupCheckAntigravityAlias } from '../../../providers/auth/antigravity-warmup.js';
19
+ import { shutdownCamoufoxLaunchers } from '../../../providers/core/config/camoufox-launcher.js';
17
20
  import { AuthFileResolver } from '../../../config/auth-file-resolver.js';
18
21
  import { buildProviderProfiles } from '../../../providers/profile/provider-profile-loader.js';
19
22
  import { isStageLoggingEnabled, logPipelineStage } from '../../utils/stage-logger.js';
@@ -22,7 +25,7 @@ import { registerHttpRoutes, registerOAuthPortalRoute } from './routes.js';
22
25
  import { mapProviderProtocol, normalizeProviderType, resolveProviderIdentity, asRecord } from './provider-utils.js';
23
26
  import { resolveRepoRoot } from './llmswitch-loader.js';
24
27
  import { enhanceProviderRequestId } from '../../utils/request-id-manager.js';
25
- import { convertProviderResponse as bridgeConvertProviderResponse, createSnapshotRecorder as bridgeCreateSnapshotRecorder, rebindResponsesConversationRequestId, extractSessionIdentifiersFromMetadata, bootstrapVirtualRouterConfig, getProviderSuccessCenter, getHubPipelineCtor } from '../../../modules/llmswitch/bridge.js';
28
+ import { convertProviderResponse as bridgeConvertProviderResponse, createSnapshotRecorder as bridgeCreateSnapshotRecorder, rebindResponsesConversationRequestId, extractSessionIdentifiersFromMetadata, bootstrapVirtualRouterConfig, getProviderSuccessCenter, getHubPipelineCtor, resolveClockConfigSnapshot, reserveClockDueTasks, commitClockDueReservation, clearClockTasksSnapshot } from '../../../modules/llmswitch/bridge.js';
26
29
  import { initializeRouteErrorHub, reportRouteError } from '../../../error-handling/route-error-hub.js';
27
30
  import { writeClientSnapshot } from '../../../providers/core/utils/snapshot-writer.js';
28
31
  import { createServerColoredLogger } from './colored-logger.js';
@@ -38,11 +41,71 @@ import { canonicalizeServerId } from './server-id.js';
38
41
  import { StatsManager } from './stats-manager.js';
39
42
  import { loadRouteCodexConfig } from '../../../config/routecodex-config-loader.js';
40
43
  import { buildInfo } from '../../../build-info.js';
44
+ import { getClockClientRegistry } from './clock-client-registry.js';
45
+ import { toExactMatchClockConfig } from './clock-daemon-inject-config.js';
46
+ import { isTmuxSessionAlive, killManagedTmuxSession } from './tmux-session-probe.js';
47
+ import { terminateManagedClientProcess } from './managed-process-probe.js';
41
48
  import { recordHubShadowCompareDiff, resolveHubShadowCompareConfig, shouldRunHubShadowCompare } from './hub-shadow-compare.js';
42
49
  import { recordLlmsEngineShadowDiff, isLlmsEngineShadowEnabledForSubpath, resolveLlmsEngineShadowConfig, shouldRunLlmsEngineShadowForSubpath } from '../../../utils/llms-engine-shadow.js';
43
50
  import { resolveLlmswitchCoreVersion } from '../../../utils/runtime-versions.js';
44
51
  const DEFAULT_MAX_PROVIDER_ATTEMPTS = 6;
45
52
  const DEFAULT_ANTIGRAVITY_MAX_PROVIDER_ATTEMPTS = 20;
53
+ const RETRYABLE_SSE_ERROR_CODE_HINTS = [
54
+ 'internal_network_failure',
55
+ 'network_error',
56
+ 'api_connection_error',
57
+ 'service_unavailable',
58
+ 'internal_server_error',
59
+ 'overloaded_error',
60
+ 'rate_limit_error',
61
+ 'request_timeout',
62
+ 'timeout'
63
+ ];
64
+ const RETRYABLE_SSE_MESSAGE_HINTS = [
65
+ 'internal network failure',
66
+ 'network failure',
67
+ 'network error',
68
+ 'temporarily unavailable',
69
+ 'temporarily unreachable',
70
+ 'upstream disconnected',
71
+ 'connection reset',
72
+ 'connection closed',
73
+ 'timed out',
74
+ 'timeout'
75
+ ];
76
+ function firstNonEmptyString(candidates) {
77
+ for (const candidate of candidates) {
78
+ if (typeof candidate !== 'string') {
79
+ continue;
80
+ }
81
+ const trimmed = candidate.trim();
82
+ if (trimmed) {
83
+ return trimmed;
84
+ }
85
+ }
86
+ return undefined;
87
+ }
88
+ function firstFiniteNumber(candidates) {
89
+ for (const candidate of candidates) {
90
+ if (typeof candidate === 'number' && Number.isFinite(candidate)) {
91
+ return candidate;
92
+ }
93
+ }
94
+ return undefined;
95
+ }
96
+ function isRetryableSseWrapperError(message, errorCode, status) {
97
+ if (typeof status === 'number' && Number.isFinite(status)) {
98
+ if (status === 408 || status === 425 || status === 429 || status >= 500) {
99
+ return true;
100
+ }
101
+ }
102
+ const normalizedCode = typeof errorCode === 'string' ? errorCode.trim().toLowerCase() : '';
103
+ if (normalizedCode && RETRYABLE_SSE_ERROR_CODE_HINTS.some((hint) => normalizedCode.includes(hint))) {
104
+ return true;
105
+ }
106
+ const loweredMessage = message.toLowerCase();
107
+ return RETRYABLE_SSE_MESSAGE_HINTS.some((hint) => loweredMessage.includes(hint));
108
+ }
46
109
  function resolveMaxProviderAttempts() {
47
110
  const raw = String(process.env.ROUTECODEX_MAX_PROVIDER_ATTEMPTS || process.env.RCC_MAX_PROVIDER_ATTEMPTS || '')
48
111
  .trim()
@@ -116,6 +179,10 @@ export class RouteCodexHttpServer {
116
179
  hubPolicyMode = null;
117
180
  hubPipelineEngineShadow = null;
118
181
  hubPipelineConfigForShadow = null;
182
+ clockDaemonInjectTimer = null;
183
+ clockDaemonInjectTickInFlight = false;
184
+ lastClockDaemonInjectErrorAtMs = 0;
185
+ lastClockDaemonCleanupAtMs = 0;
119
186
  constructor(config) {
120
187
  this.config = config;
121
188
  this.app = express();
@@ -318,6 +385,9 @@ export class RouteCodexHttpServer {
318
385
  if (!patched.defaultModel && profile.metadata?.defaultModel) {
319
386
  patched.defaultModel = profile.metadata.defaultModel;
320
387
  }
388
+ if (!patched.deepseek && profile.metadata?.deepseek) {
389
+ patched.deepseek = profile.metadata.deepseek;
390
+ }
321
391
  return this.canonicalizeRuntimeProvider(patched);
322
392
  }
323
393
  canonicalizeRuntimeProvider(runtime) {
@@ -499,6 +569,200 @@ export class RouteCodexHttpServer {
499
569
  }
500
570
  return true;
501
571
  }
572
+ shouldEnableClockDaemonInjectLoop() {
573
+ const raw = String(process.env.ROUTECODEX_CLOCK_DAEMON_INJECT_ENABLE || process.env.RCC_CLOCK_DAEMON_INJECT_ENABLE || '').trim().toLowerCase();
574
+ if (raw === '0' || raw === 'false' || raw === 'off' || raw === 'no') {
575
+ return false;
576
+ }
577
+ if (raw === '1' || raw === 'true' || raw === 'on' || raw === 'yes') {
578
+ return true;
579
+ }
580
+ if (process.env.JEST_WORKER_ID || process.env.NODE_ENV === 'test') {
581
+ return false;
582
+ }
583
+ return true;
584
+ }
585
+ resolveRawClockConfig() {
586
+ const user = this.userConfig && typeof this.userConfig === 'object' ? this.userConfig : {};
587
+ const vr = user.virtualrouter && typeof user.virtualrouter === 'object' ? user.virtualrouter : null;
588
+ if (vr && Object.prototype.hasOwnProperty.call(vr, 'clock')) {
589
+ return vr.clock;
590
+ }
591
+ if (Object.prototype.hasOwnProperty.call(user, 'clock')) {
592
+ return user.clock;
593
+ }
594
+ const artCfg = this.currentRouterArtifacts &&
595
+ this.currentRouterArtifacts.config &&
596
+ typeof this.currentRouterArtifacts.config === 'object'
597
+ ? this.currentRouterArtifacts.config
598
+ : null;
599
+ if (artCfg && Object.prototype.hasOwnProperty.call(artCfg, 'clock')) {
600
+ return artCfg.clock;
601
+ }
602
+ return undefined;
603
+ }
604
+ stopClockDaemonInjectLoop() {
605
+ if (this.clockDaemonInjectTimer) {
606
+ clearInterval(this.clockDaemonInjectTimer);
607
+ this.clockDaemonInjectTimer = null;
608
+ }
609
+ }
610
+ startClockDaemonInjectLoop() {
611
+ this.stopClockDaemonInjectLoop();
612
+ if (!this.shouldEnableClockDaemonInjectLoop()) {
613
+ return;
614
+ }
615
+ const rawTick = String(process.env.ROUTECODEX_CLOCK_DAEMON_INJECT_TICK_MS || process.env.RCC_CLOCK_DAEMON_INJECT_TICK_MS || '').trim();
616
+ const parsedTick = rawTick ? Number.parseInt(rawTick, 10) : NaN;
617
+ const tickMs = Number.isFinite(parsedTick) && parsedTick >= 200 ? Math.floor(parsedTick) : 1500;
618
+ this.clockDaemonInjectTimer = setInterval(() => {
619
+ void this.tickClockDaemonInjectLoop();
620
+ }, tickMs);
621
+ this.clockDaemonInjectTimer.unref?.();
622
+ void this.tickClockDaemonInjectLoop();
623
+ }
624
+ async tickClockDaemonInjectLoop() {
625
+ if (this.clockDaemonInjectTickInFlight) {
626
+ return;
627
+ }
628
+ this.clockDaemonInjectTickInFlight = true;
629
+ try {
630
+ const rawClockConfig = this.resolveRawClockConfig();
631
+ const resolvedClockConfig = await resolveClockConfigSnapshot(rawClockConfig);
632
+ if (!resolvedClockConfig) {
633
+ return;
634
+ }
635
+ const clockConfig = toExactMatchClockConfig(resolvedClockConfig);
636
+ const sessionDir = String(process.env.ROUTECODEX_SESSION_DIR || '').trim();
637
+ const clockDir = sessionDir ? path.join(sessionDir, 'clock') : '';
638
+ const entries = clockDir
639
+ ? await fs.readdir(clockDir, { withFileTypes: true }).catch(() => [])
640
+ : [];
641
+ const registry = getClockClientRegistry();
642
+ const now = Date.now();
643
+ const cleanupRequestId = `clock_cleanup_${now}_${Math.random().toString(16).slice(2, 8)}`;
644
+ const staleCleanup = registry.cleanupStaleHeartbeats({
645
+ nowMs: now,
646
+ terminateManagedTmuxSession: (tmuxSessionId) => killManagedTmuxSession(tmuxSessionId),
647
+ terminateManagedClientProcess: (processInfo) => terminateManagedClientProcess(processInfo)
648
+ });
649
+ const deadTmuxCleanup = registry.cleanupDeadTmuxSessions({
650
+ isTmuxSessionAlive,
651
+ terminateManagedTmuxSession: (tmuxSessionId) => killManagedTmuxSession(tmuxSessionId),
652
+ terminateManagedClientProcess: (processInfo) => terminateManagedClientProcess(processInfo)
653
+ });
654
+ const removedConversationSessionIds = Array.from(new Set([
655
+ ...staleCleanup.removedConversationSessionIds,
656
+ ...deadTmuxCleanup.removedConversationSessionIds
657
+ ]));
658
+ if (removedConversationSessionIds.length > 0) {
659
+ for (const removedConversationSessionId of removedConversationSessionIds) {
660
+ await clearClockTasksSnapshot({
661
+ sessionId: removedConversationSessionId,
662
+ config: clockConfig
663
+ });
664
+ }
665
+ }
666
+ const hasCleanupActions = staleCleanup.removedDaemonIds.length > 0
667
+ || deadTmuxCleanup.removedDaemonIds.length > 0;
668
+ if (hasCleanupActions && Date.now() - this.lastClockDaemonCleanupAtMs > 2000) {
669
+ this.lastClockDaemonCleanupAtMs = Date.now();
670
+ console.log('[RouteCodexHttpServer] clock daemon cleanup audit:', {
671
+ requestId: cleanupRequestId,
672
+ staleHeartbeat: {
673
+ reason: 'heartbeat_timeout',
674
+ staleAfterMs: staleCleanup.staleAfterMs,
675
+ removedDaemonIds: staleCleanup.removedDaemonIds,
676
+ removedTmuxSessionIds: staleCleanup.removedTmuxSessionIds,
677
+ removedConversationSessionIds: staleCleanup.removedConversationSessionIds,
678
+ killedTmuxSessionIds: staleCleanup.killedTmuxSessionIds,
679
+ failedKillTmuxSessionIds: staleCleanup.failedKillTmuxSessionIds,
680
+ skippedKillTmuxSessionIds: staleCleanup.skippedKillTmuxSessionIds,
681
+ killedManagedClientPids: staleCleanup.killedManagedClientPids,
682
+ failedKillManagedClientPids: staleCleanup.failedKillManagedClientPids,
683
+ skippedKillManagedClientPids: staleCleanup.skippedKillManagedClientPids
684
+ },
685
+ deadTmux: {
686
+ reason: 'tmux_not_alive',
687
+ removedDaemonIds: deadTmuxCleanup.removedDaemonIds,
688
+ removedTmuxSessionIds: deadTmuxCleanup.removedTmuxSessionIds,
689
+ removedConversationSessionIds: deadTmuxCleanup.removedConversationSessionIds,
690
+ killedTmuxSessionIds: deadTmuxCleanup.killedTmuxSessionIds,
691
+ failedKillTmuxSessionIds: deadTmuxCleanup.failedKillTmuxSessionIds,
692
+ skippedKillTmuxSessionIds: deadTmuxCleanup.skippedKillTmuxSessionIds,
693
+ killedManagedClientPids: deadTmuxCleanup.killedManagedClientPids,
694
+ failedKillManagedClientPids: deadTmuxCleanup.failedKillManagedClientPids,
695
+ skippedKillManagedClientPids: deadTmuxCleanup.skippedKillManagedClientPids
696
+ }
697
+ });
698
+ }
699
+ for (const entry of entries) {
700
+ if (!entry || typeof entry.name !== 'string') {
701
+ continue;
702
+ }
703
+ if (!entry.name.endsWith('.json')) {
704
+ continue;
705
+ }
706
+ if (typeof entry.isFile === 'function' && !entry.isFile()) {
707
+ continue;
708
+ }
709
+ const sessionId = entry.name.slice(0, -'.json'.length).trim();
710
+ if (!sessionId) {
711
+ continue;
712
+ }
713
+ const reservationId = 'clockd_inject_' + now + '_' + Math.random().toString(16).slice(2, 8);
714
+ const reserved = await reserveClockDueTasks({
715
+ reservationId,
716
+ sessionId,
717
+ config: clockConfig,
718
+ requestId: reservationId
719
+ });
720
+ if (!reserved || !reserved.reservation || typeof reserved.injectText !== 'string' || !reserved.injectText.trim()) {
721
+ continue;
722
+ }
723
+ const bind = registry.bindConversationSession({ conversationSessionId: sessionId });
724
+ const text = [
725
+ '[Clock Reminder]: scheduled tasks are due.',
726
+ reserved.injectText.trim(),
727
+ 'You may call tools to complete these tasks.',
728
+ 'MANDATORY: if waiting is needed, use the clock tool to schedule wake-up (clock.schedule) now; do not only promise to wait.'
729
+ ].join('\n');
730
+ const injected = await registry.inject({
731
+ sessionId,
732
+ text,
733
+ requestId: reservationId,
734
+ source: 'clock.daemon.inject'
735
+ });
736
+ if (!injected.ok) {
737
+ const nowWarn = Date.now();
738
+ if (nowWarn - this.lastClockDaemonInjectErrorAtMs > 5000) {
739
+ this.lastClockDaemonInjectErrorAtMs = nowWarn;
740
+ console.warn('[RouteCodexHttpServer] clock daemon inject skipped:', {
741
+ sessionId,
742
+ injectReason: injected.reason,
743
+ bindOk: bind.ok,
744
+ bindReason: bind.reason
745
+ });
746
+ }
747
+ continue;
748
+ }
749
+ await commitClockDueReservation({
750
+ reservation: reserved.reservation,
751
+ config: clockConfig
752
+ });
753
+ }
754
+ }
755
+ catch (error) {
756
+ const now = Date.now();
757
+ if (now - this.lastClockDaemonInjectErrorAtMs > 5000) {
758
+ this.lastClockDaemonInjectErrorAtMs = now;
759
+ console.warn('[RouteCodexHttpServer] clock daemon inject loop tick failed:', error);
760
+ }
761
+ }
762
+ finally {
763
+ this.clockDaemonInjectTickInFlight = false;
764
+ }
765
+ }
502
766
  /**
503
767
  * 初始化服务器
504
768
  */
@@ -610,7 +874,12 @@ export class RouteCodexHttpServer {
610
874
  return new Promise((resolve, reject) => {
611
875
  this.server = this.app.listen(this.config.server.port, this.config.server.host, () => {
612
876
  this._isRunning = true;
613
- console.log(`[RouteCodexHttpServer] Server started on ${this.config.server.host}:${this.config.server.port}`);
877
+ const boundAddress = this.server?.address();
878
+ const resolvedPort = boundAddress && typeof boundAddress === 'object' && typeof boundAddress.port === 'number'
879
+ ? boundAddress.port
880
+ : this.config.server.port;
881
+ process.env.ROUTECODEX_SERVER_PORT = String(resolvedPort);
882
+ console.log(`[RouteCodexHttpServer] Server started on ${this.config.server.host}:${resolvedPort}`);
614
883
  resolve();
615
884
  });
616
885
  // In test runners (Jest), prevent the listen handle from keeping the process alive
@@ -639,6 +908,16 @@ export class RouteCodexHttpServer {
639
908
  * 停止服务器
640
909
  */
641
910
  async stop() {
911
+ this.stopClockDaemonInjectLoop();
912
+ try {
913
+ await shutdownCamoufoxLaunchers();
914
+ }
915
+ catch {
916
+ // ignore launcher cleanup errors
917
+ }
918
+ if (!this.server) {
919
+ return;
920
+ }
642
921
  if (this.server) {
643
922
  // Best-effort: close any open keep-alive sockets so server.close can finish.
644
923
  for (const socket of this.activeSockets) {
@@ -879,6 +1158,7 @@ export class RouteCodexHttpServer {
879
1158
  this.hubPipelineConfigForShadow = hubConfig;
880
1159
  this.hubPipelineEngineShadow = null;
881
1160
  await this.initializeProviderRuntimes(bootstrapArtifacts);
1161
+ this.startClockDaemonInjectLoop();
882
1162
  }
883
1163
  buildHandlerContext() {
884
1164
  return {
@@ -1177,6 +1457,21 @@ export class RouteCodexHttpServer {
1177
1457
  if (rawType === 'iflow-cookie') {
1178
1458
  return { ...auth, type: 'apikey', rawType: auth.rawType ?? 'iflow-cookie' };
1179
1459
  }
1460
+ if (rawType === 'deepseek-account') {
1461
+ // Compatibility note:
1462
+ // legacy deepseek configs may still carry value/secretRef/mobile/password/clientId/clientSecret.
1463
+ // Runtime auth for deepseek-account is tokenFile(+alias)-only; we strip legacy fields instead of
1464
+ // failing init so existing deployments can migrate without downtime.
1465
+ const tokenFile = pickString(authRecord.tokenFile, authRecord.token_file);
1466
+ const accountAlias = pickString(authRecord.accountAlias, authRecord.account_alias, runtime.keyAlias);
1467
+ return {
1468
+ type: 'apikey',
1469
+ rawType: auth.rawType ?? 'deepseek-account',
1470
+ value: '',
1471
+ ...(accountAlias ? { accountAlias } : {}),
1472
+ ...(tokenFile ? { tokenFile } : {})
1473
+ };
1474
+ }
1180
1475
  const value = await this.resolveApiKeyValue(runtime, auth);
1181
1476
  return { ...auth, type: 'apikey', value };
1182
1477
  }
@@ -1267,6 +1562,7 @@ export class RouteCodexHttpServer {
1267
1562
  }
1268
1563
  }));
1269
1564
  this.providerHandles.clear();
1565
+ ProviderFactory.clearInstanceCache?.();
1270
1566
  }
1271
1567
  async executePipeline(input) {
1272
1568
  if (!this.isPipelineReady()) {
@@ -1337,7 +1633,26 @@ export class RouteCodexHttpServer {
1337
1633
  stream: metadataForIteration.stream
1338
1634
  });
1339
1635
  const originalRequestSnapshot = this.cloneRequestPayload(input.body);
1340
- const pipelineResult = await this.runHubPipeline(input, metadataForIteration);
1636
+ let pipelineResult;
1637
+ try {
1638
+ pipelineResult = await this.runHubPipeline(input, metadataForIteration);
1639
+ }
1640
+ catch (pipelineError) {
1641
+ const pipelineErrorCode = typeof pipelineError.code === 'string'
1642
+ ? String(pipelineError.code).trim()
1643
+ : '';
1644
+ const pipelineErrorMessage = pipelineError instanceof Error
1645
+ ? pipelineError.message
1646
+ : String(pipelineError ?? 'Unknown error');
1647
+ const isPoolExhaustedError = pipelineErrorCode === 'PROVIDER_NOT_AVAILABLE' ||
1648
+ pipelineErrorCode === 'ERR_NO_PROVIDER_TARGET' ||
1649
+ /all providers unavailable/i.test(pipelineErrorMessage) ||
1650
+ /virtual router did not produce a provider target/i.test(pipelineErrorMessage);
1651
+ if (firstError && isPoolExhaustedError) {
1652
+ throw firstError;
1653
+ }
1654
+ throw pipelineError;
1655
+ }
1341
1656
  const pipelineMetadata = pipelineResult.metadata ?? {};
1342
1657
  const mergedMetadata = { ...metadataForIteration, ...pipelineMetadata };
1343
1658
  this.logStage(`${pipelineLabel}.completed`, providerRequestId, {
@@ -1370,6 +1685,33 @@ export class RouteCodexHttpServer {
1370
1685
  requestId: input.requestId,
1371
1686
  retryable: true
1372
1687
  });
1688
+ try {
1689
+ const { emitProviderError } = await import('../../../providers/core/utils/provider-error-reporter.js');
1690
+ emitProviderError({
1691
+ error,
1692
+ stage: 'provider.runtime.resolve',
1693
+ runtime: {
1694
+ requestId: input.requestId,
1695
+ providerKey: target.providerKey,
1696
+ providerId: target.providerKey.split('.')[0],
1697
+ providerType: String(target.providerType || 'unknown'),
1698
+ providerProtocol: String(target.outboundProfile || ''),
1699
+ routeName: pipelineResult.routingDecision?.routeName,
1700
+ pipelineId: target.providerKey,
1701
+ target
1702
+ },
1703
+ dependencies: this.getModuleDependencies(),
1704
+ recoverable: false,
1705
+ affectsHealth: true,
1706
+ details: {
1707
+ reason: 'runtime_not_initialized',
1708
+ providerKey: target.providerKey
1709
+ }
1710
+ });
1711
+ }
1712
+ catch {
1713
+ // best-effort
1714
+ }
1373
1715
  if (!firstError) {
1374
1716
  firstError = error;
1375
1717
  }
@@ -1393,6 +1735,35 @@ export class RouteCodexHttpServer {
1393
1735
  requestId: input.requestId,
1394
1736
  retryable: true
1395
1737
  });
1738
+ try {
1739
+ const { emitProviderError } = await import('../../../providers/core/utils/provider-error-reporter.js');
1740
+ emitProviderError({
1741
+ error: initError ?? error,
1742
+ stage: 'provider.runtime.resolve',
1743
+ runtime: {
1744
+ requestId: input.requestId,
1745
+ providerKey: target.providerKey,
1746
+ providerId: target.providerKey.split('.')[0],
1747
+ providerType: String(target.providerType || 'unknown'),
1748
+ providerProtocol: String(target.outboundProfile || ''),
1749
+ routeName: pipelineResult.routingDecision?.routeName,
1750
+ pipelineId: target.providerKey,
1751
+ runtimeKey,
1752
+ target
1753
+ },
1754
+ dependencies: this.getModuleDependencies(),
1755
+ recoverable: false,
1756
+ affectsHealth: true,
1757
+ details: {
1758
+ reason: 'runtime_handle_missing',
1759
+ providerKey: target.providerKey,
1760
+ runtimeKey
1761
+ }
1762
+ });
1763
+ }
1764
+ catch {
1765
+ // best-effort
1766
+ }
1396
1767
  if (!firstError) {
1397
1768
  firstError = error;
1398
1769
  }
@@ -1511,6 +1882,61 @@ export class RouteCodexHttpServer {
1511
1882
  response: normalized,
1512
1883
  pipelineMetadata: mergedMetadata
1513
1884
  });
1885
+ // Treat upstream 429 as provider failure across protocols to avoid
1886
+ // silently returning success and to let Virtual Router failover to other candidates.
1887
+ // Keep existing Gemini compatibility behavior for 400/4xx thoughtSignature-like failures.
1888
+ const convertedStatus = typeof converted.status === 'number' ? converted.status : undefined;
1889
+ const isGlobalRetryable429 = convertedStatus === 429;
1890
+ const isGeminiCompatFailure = typeof convertedStatus === 'number' &&
1891
+ convertedStatus >= 400 &&
1892
+ (isAntigravityProviderKey(target.providerKey) ||
1893
+ (typeof target.providerKey === 'string' && target.providerKey.startsWith('gemini-cli.'))) &&
1894
+ providerProtocol === 'gemini-chat';
1895
+ if (isGlobalRetryable429 || isGeminiCompatFailure) {
1896
+ const bodyForError = converted.body && typeof converted.body === 'object'
1897
+ ? converted.body
1898
+ : undefined;
1899
+ const errMsg = bodyForError && bodyForError.error && typeof bodyForError.error === 'object'
1900
+ ? String(bodyForError.error.message || bodyForError.error || '')
1901
+ : '';
1902
+ const statusCode = typeof convertedStatus === 'number' ? convertedStatus : 500;
1903
+ const errorToThrow = new Error(errMsg && errMsg.trim().length ? errMsg : `HTTP ${statusCode}`);
1904
+ errorToThrow.statusCode = statusCode;
1905
+ errorToThrow.status = statusCode;
1906
+ errorToThrow.response = { data: bodyForError };
1907
+ try {
1908
+ const { emitProviderError } = await import('../../../providers/core/utils/provider-error-reporter.js');
1909
+ emitProviderError({
1910
+ error: errorToThrow,
1911
+ stage: 'provider.http',
1912
+ runtime: {
1913
+ requestId: input.requestId,
1914
+ providerKey: target.providerKey,
1915
+ providerId: handle.providerId,
1916
+ providerType: handle.providerType,
1917
+ providerFamily: handle.providerFamily,
1918
+ providerProtocol,
1919
+ routeName: pipelineResult.routingDecision?.routeName,
1920
+ pipelineId: target.providerKey,
1921
+ target,
1922
+ runtimeKey
1923
+ },
1924
+ dependencies: this.getModuleDependencies(),
1925
+ statusCode,
1926
+ recoverable: statusCode === 429,
1927
+ affectsHealth: true,
1928
+ details: {
1929
+ source: 'converted_response_status',
1930
+ convertedStatus: statusCode,
1931
+ wrappedErrorResponse: true
1932
+ }
1933
+ });
1934
+ }
1935
+ catch {
1936
+ // best-effort; never block retry/failover path
1937
+ }
1938
+ throw errorToThrow;
1939
+ }
1514
1940
  const usage = this.extractUsageFromResult(converted, mergedMetadata);
1515
1941
  // QuotaManager listens to provider error/success events; avoid duplicating accounting here.
1516
1942
  this.stats.recordCompletion(statsRequestId, { usage, error: false });
@@ -2000,12 +2426,21 @@ export class RouteCodexHttpServer {
2000
2426
  if (body && typeof body === 'object') {
2001
2427
  const wrapperError = this.extractSseWrapperError(body);
2002
2428
  if (wrapperError) {
2003
- const error = new Error(`[RouteCodexHttpServer] Upstream SSE terminated: ${wrapperError}`);
2429
+ const codeSuffix = wrapperError.errorCode ? ` [${wrapperError.errorCode}]` : '';
2430
+ const error = new Error(`Upstream SSE error event${codeSuffix}: ${wrapperError.message}`);
2004
2431
  error.code = 'SSE_DECODE_ERROR';
2432
+ if (wrapperError.errorCode) {
2433
+ error.upstreamCode = wrapperError.errorCode;
2434
+ }
2435
+ error.retryable = wrapperError.retryable;
2436
+ if (wrapperError.retryable) {
2437
+ error.status = 503;
2438
+ error.statusCode = 503;
2439
+ }
2005
2440
  throw error;
2006
2441
  }
2007
2442
  }
2008
- if (options.processMode === 'passthrough') {
2443
+ if (options.processMode === 'passthrough' && !options.wantsStream) {
2009
2444
  return options.response;
2010
2445
  }
2011
2446
  const entry = (options.entryEndpoint || '').toLowerCase();
@@ -2039,6 +2474,12 @@ export class RouteCodexHttpServer {
2039
2474
  const baseContext = {
2040
2475
  ...(metadataBag ?? {})
2041
2476
  };
2477
+ if (baseContext.capturedChatRequest === undefined &&
2478
+ options.originalRequest &&
2479
+ typeof options.originalRequest === 'object' &&
2480
+ !Array.isArray(options.originalRequest)) {
2481
+ baseContext.capturedChatRequest = options.originalRequest;
2482
+ }
2042
2483
  // 将 HubPipeline metadata.routeName 映射为 AdapterContext.routeId,
2043
2484
  // 便于 llmswitch-core 在第三跳中使用 routeHint 复用首次路由决策。
2044
2485
  if (typeof metadataBag?.routeName === 'string') {
@@ -2181,10 +2622,11 @@ export class RouteCodexHttpServer {
2181
2622
  if (!record || typeof record !== 'object' || depth < 0) {
2182
2623
  return undefined;
2183
2624
  }
2184
- const mode = record.mode;
2185
- const errVal = record.error;
2186
- if (mode === 'sse' && typeof errVal === 'string' && errVal.trim()) {
2187
- return errVal.trim();
2625
+ if (record.mode === 'sse') {
2626
+ const normalized = this.normalizeSseWrapperErrorValue(record.error, depth);
2627
+ if (normalized) {
2628
+ return normalized;
2629
+ }
2188
2630
  }
2189
2631
  const nestedKeys = ['body', 'data', 'payload', 'response'];
2190
2632
  for (const key of nestedKeys) {
@@ -2199,6 +2641,89 @@ export class RouteCodexHttpServer {
2199
2641
  }
2200
2642
  return undefined;
2201
2643
  }
2644
+ normalizeSseWrapperErrorValue(value, depth) {
2645
+ if (value === undefined || value === null || depth < 0) {
2646
+ return undefined;
2647
+ }
2648
+ if (typeof value === 'string') {
2649
+ const trimmed = value.trim();
2650
+ if (!trimmed) {
2651
+ return undefined;
2652
+ }
2653
+ if (depth > 0 && (trimmed.startsWith('{') || trimmed.startsWith('['))) {
2654
+ try {
2655
+ const parsed = JSON.parse(trimmed);
2656
+ const parsedInfo = this.normalizeSseWrapperErrorValue(parsed, depth - 1);
2657
+ if (parsedInfo) {
2658
+ return parsedInfo;
2659
+ }
2660
+ }
2661
+ catch {
2662
+ // fallback to raw string
2663
+ }
2664
+ }
2665
+ return {
2666
+ message: trimmed,
2667
+ retryable: isRetryableSseWrapperError(trimmed)
2668
+ };
2669
+ }
2670
+ if (typeof value !== 'object' || Array.isArray(value)) {
2671
+ return undefined;
2672
+ }
2673
+ const record = value;
2674
+ const directMessage = firstNonEmptyString([
2675
+ record.message,
2676
+ record.error_message,
2677
+ record.errorMessage
2678
+ ]);
2679
+ const directCode = firstNonEmptyString([
2680
+ record.code,
2681
+ record.error_code,
2682
+ record.errorCode,
2683
+ record.type
2684
+ ]);
2685
+ const directStatus = firstFiniteNumber([
2686
+ record.status,
2687
+ record.statusCode,
2688
+ record.status_code,
2689
+ record.http_status
2690
+ ]);
2691
+ if (depth > 0) {
2692
+ for (const key of ['error', 'data', 'payload', 'details', 'body', 'response']) {
2693
+ const nestedInfo = this.normalizeSseWrapperErrorValue(record[key], depth - 1);
2694
+ if (nestedInfo) {
2695
+ const mergedCode = nestedInfo.errorCode ?? directCode;
2696
+ const retryable = nestedInfo.retryable || isRetryableSseWrapperError(nestedInfo.message, mergedCode, directStatus);
2697
+ return {
2698
+ message: nestedInfo.message,
2699
+ ...(mergedCode ? { errorCode: mergedCode } : {}),
2700
+ retryable
2701
+ };
2702
+ }
2703
+ }
2704
+ }
2705
+ if (directMessage) {
2706
+ return {
2707
+ message: directMessage,
2708
+ ...(directCode ? { errorCode: directCode } : {}),
2709
+ retryable: isRetryableSseWrapperError(directMessage, directCode, directStatus)
2710
+ };
2711
+ }
2712
+ try {
2713
+ const serialized = JSON.stringify(record);
2714
+ if (serialized && serialized !== '{}') {
2715
+ return {
2716
+ message: serialized,
2717
+ ...(directCode ? { errorCode: directCode } : {}),
2718
+ retryable: isRetryableSseWrapperError(serialized, directCode, directStatus)
2719
+ };
2720
+ }
2721
+ }
2722
+ catch {
2723
+ // ignore stringify failures
2724
+ }
2725
+ return undefined;
2726
+ }
2202
2727
  extractClientModelId(metadata, originalRequest) {
2203
2728
  const candidates = [
2204
2729
  metadata.clientModelId,