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