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