@jsonstudio/rcc 0.89.3 → 0.89.168
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 +240 -179
- package/config/modules.json +1 -11
- package/dist/build-info.js +3 -3
- package/dist/build-info.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.d.ts +16 -0
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +56 -0
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -0
- package/dist/client/openai/chat-protocol-client.js.map +1 -1
- package/dist/config/modules.json +1 -11
- package/dist/core/provider-health-manager.d.ts +17 -0
- package/dist/core/provider-health-manager.js +66 -0
- package/dist/core/provider-health-manager.js.map +1 -0
- package/dist/error-handling/route-error-hub.d.ts +48 -0
- package/dist/error-handling/route-error-hub.js +131 -0
- package/dist/error-handling/route-error-hub.js.map +1 -0
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/llmswitch/bridge.d.ts +2 -0
- package/dist/modules/llmswitch/bridge.js +17 -0
- package/dist/modules/llmswitch/bridge.js.map +1 -1
- package/dist/modules/pipeline/utils/colored-logger.d.ts +14 -0
- package/dist/modules/pipeline/utils/colored-logger.js +48 -0
- package/dist/modules/pipeline/utils/colored-logger.js.map +1 -0
- package/dist/modules/pipeline/utils/debug-logger.d.ts +2 -0
- package/dist/modules/pipeline/utils/debug-logger.js +36 -0
- package/dist/modules/pipeline/utils/debug-logger.js.map +1 -1
- package/dist/providers/auth/gemini-cli-userinfo-helper.d.ts +53 -0
- package/dist/providers/auth/gemini-cli-userinfo-helper.js +152 -0
- package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -0
- package/dist/providers/auth/oauth-auth.js +3 -2
- package/dist/providers/auth/oauth-auth.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle.js +21 -20
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/auth/oauth-logger.d.ts +1 -0
- package/dist/providers/auth/oauth-logger.js +21 -0
- package/dist/providers/auth/oauth-logger.js.map +1 -0
- package/dist/providers/compat/compat-directory-loader.js +2 -55
- package/dist/providers/compat/compat-directory-loader.js.map +1 -1
- package/dist/providers/compat/compatibility-factory.d.ts +4 -4
- package/dist/providers/compat/compatibility-factory.js +108 -0
- package/dist/providers/compat/compatibility-factory.js.map +1 -1
- package/dist/providers/compat/glm/glm-compatibility.d.ts +2 -2
- package/dist/providers/compat/glm/glm-compatibility.js +7 -7
- package/dist/providers/compat/glm/glm-compatibility.js.map +1 -1
- package/dist/providers/compat/glm/index.js +0 -6
- package/dist/providers/compat/glm/index.js.map +1 -1
- package/dist/providers/compat/iflow/iflow-compatibility.d.ts +1 -1
- package/dist/providers/compat/iflow/iflow-compatibility.js +6 -6
- package/dist/providers/compat/iflow/iflow-compatibility.js.map +1 -1
- package/dist/providers/compat/index.d.ts +0 -6
- package/dist/providers/compat/index.js +0 -7
- package/dist/providers/compat/index.js.map +1 -1
- package/dist/providers/compat/lmstudio-compatibility.d.ts +2 -2
- package/dist/providers/compat/lmstudio-compatibility.js +4 -4
- package/dist/providers/compat/lmstudio-compatibility.js.map +1 -1
- package/dist/providers/compat/passthrough-compatibility.d.ts +1 -1
- package/dist/providers/compat/passthrough-compatibility.js +3 -3
- package/dist/providers/compat/passthrough-compatibility.js.map +1 -1
- package/dist/providers/compat/profiles/chat/glm/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/glm/index.js +6 -0
- package/dist/providers/compat/profiles/chat/glm/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/iflow/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/iflow/index.js +6 -0
- package/dist/providers/compat/profiles/chat/iflow/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.js +6 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/qwen/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/qwen/index.js +6 -0
- package/dist/providers/compat/profiles/chat/qwen/index.js.map +1 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.d.ts +6 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.js +6 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/c4m/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/c4m/index.js +6 -0
- package/dist/providers/compat/profiles/responses/c4m/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/default/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/default/index.js +6 -0
- package/dist/providers/compat/profiles/responses/default/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/fai/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/fai/index.js +6 -0
- package/dist/providers/compat/profiles/responses/fai/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/fc/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/fc/index.js +6 -0
- package/dist/providers/compat/profiles/responses/fc/index.js.map +1 -0
- package/dist/providers/compat/qwen/index.js +0 -6
- package/dist/providers/compat/qwen/index.js.map +1 -1
- package/dist/providers/compat/qwen-compatibility.d.ts +2 -2
- package/dist/providers/compat/qwen-compatibility.js +4 -4
- package/dist/providers/compat/qwen-compatibility.js.map +1 -1
- package/dist/providers/compat/register-compat-module.d.ts +8 -0
- package/dist/providers/compat/register-compat-module.js +53 -0
- package/dist/providers/compat/register-compat-module.js.map +1 -0
- package/dist/providers/compat/responses/c4m-responses-compatibility.d.ts +6 -2
- package/dist/providers/compat/responses/c4m-responses-compatibility.js +85 -3
- package/dist/providers/compat/responses/c4m-responses-compatibility.js.map +1 -1
- package/dist/providers/compat/standard-compatibility-utils.js +45 -15
- package/dist/providers/compat/standard-compatibility-utils.js.map +1 -1
- package/dist/providers/core/api/provider-config.d.ts +1 -1
- package/dist/providers/core/api/provider-types.d.ts +3 -1
- package/dist/providers/core/api/provider-types.js +1 -0
- package/dist/providers/core/api/provider-types.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +19 -2
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/base-provider.d.ts +8 -0
- package/dist/providers/core/runtime/base-provider.js +79 -36
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +33 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +156 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -0
- package/dist/providers/core/runtime/gemini-http-provider.d.ts +1 -2
- package/dist/providers/core/runtime/gemini-http-provider.js +0 -12
- package/dist/providers/core/runtime/gemini-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.d.ts +42 -0
- package/dist/providers/core/runtime/http-request-executor.js +133 -0
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -0
- package/dist/providers/core/runtime/http-transport-provider.d.ts +7 -11
- package/dist/providers/core/runtime/http-transport-provider.js +226 -371
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-error-classifier.d.ts +25 -0
- package/dist/providers/core/runtime/provider-error-classifier.js +139 -0
- package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -0
- package/dist/providers/core/runtime/provider-error-types.d.ts +23 -0
- package/dist/providers/core/runtime/provider-error-types.js +2 -0
- package/dist/providers/core/runtime/provider-error-types.js.map +1 -0
- package/dist/providers/core/runtime/provider-factory.d.ts +1 -1
- package/dist/providers/core/runtime/provider-factory.js +14 -0
- package/dist/providers/core/runtime/provider-factory.js.map +1 -1
- package/dist/providers/core/runtime/provider-runtime-metadata.d.ts +1 -0
- package/dist/providers/core/runtime/provider-runtime-metadata.js.map +1 -1
- package/dist/providers/core/runtime/responses-provider.d.ts +7 -0
- package/dist/providers/core/runtime/responses-provider.js +228 -12
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +10 -9
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-device-flow.js +10 -9
- package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
- package/dist/providers/core/utils/provider-error-reporter.js +63 -15
- package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
- package/dist/providers/core/utils/provider-type-utils.d.ts +1 -1
- package/dist/providers/core/utils/provider-type-utils.js +6 -1
- package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.d.ts +10 -0
- package/dist/providers/core/utils/snapshot-writer.js +85 -0
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/providers/mock/mock-provider-runtime.js +44 -0
- package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
- package/dist/providers/profile/provider-profile-loader.js +26 -19
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +2 -2
- package/dist/server/handlers/chat-handler.js +9 -3
- package/dist/server/handlers/chat-handler.js.map +1 -1
- package/dist/server/handlers/handler-utils.d.ts +7 -1
- package/dist/server/handlers/handler-utils.js +64 -52
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/messages-handler.js +9 -3
- package/dist/server/handlers/messages-handler.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +21 -13
- package/dist/server/handlers/responses-handler.js.map +1 -1
- package/dist/server/runtime/http-server/colored-logger.d.ts +1 -0
- package/dist/server/runtime/http-server/colored-logger.js +33 -0
- package/dist/server/runtime/http-server/colored-logger.js.map +1 -0
- package/dist/server/runtime/http-server/index.d.ts +3 -0
- package/dist/server/runtime/http-server/index.js +76 -19
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/provider-utils.d.ts +3 -1
- package/dist/server/runtime/http-server/provider-utils.js +12 -2
- package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +6 -2
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +31 -11
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +2 -1
- package/dist/utils/error-center-payload.d.ts +7 -0
- package/dist/utils/error-center-payload.js +67 -0
- package/dist/utils/error-center-payload.js.map +1 -0
- package/dist/utils/error-handler-registry.d.ts +7 -0
- package/dist/utils/error-handler-registry.js +44 -12
- package/dist/utils/error-handler-registry.js.map +1 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/codecs/responses-openai-codec.js +16 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +36 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +37 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +18 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +45 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/compat-profiles.json +38 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +314 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/version-switch.json +150 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +4 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.js +667 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-profile-store.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-profile-store.js +76 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +62 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +110 -29
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.d.ts +14 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +23 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +34 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +4 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +26 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +71 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.d.ts +35 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.js +64 -19
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.d.ts +21 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +138 -22
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.d.ts +21 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +116 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +52 -2
- package/node_modules/@jsonstudio/llms/dist/filters/config/openai-openai.fieldmap.json +18 -0
- package/node_modules/@jsonstudio/llms/dist/filters/special/request-tools-normalize.js +20 -1
- package/node_modules/@jsonstudio/llms/dist/guidance/index.js +6 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +16 -7
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +40 -37
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/default-thinking-keywords.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/default-thinking-keywords.js +13 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +39 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +52 -11
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +340 -11
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +105 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -2
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +53 -11
- package/node_modules/@jsonstudio/llms/dist/test-output/virtual-router/results.json +1 -0
- package/node_modules/@jsonstudio/llms/dist/test-output/virtual-router/summary.json +12 -0
- package/node_modules/@jsonstudio/llms/dist/tools/tool-registry.js +4 -3
- package/node_modules/@jsonstudio/llms/package.json +3 -3
- package/package.json +13 -10
- package/scripts/analyze-routing-classifier.mjs +166 -0
- package/scripts/analyze-routing-samples.mjs +216 -0
- package/scripts/analyze-thinking-keywords.mjs +17 -2
- package/scripts/build-core.mjs +8 -0
- package/scripts/classify-sample-tools.mjs +252 -0
- package/scripts/ensure-llmswitch-mode.mjs +95 -0
- package/scripts/gen-build-info.mjs +58 -4
- package/scripts/install-global.sh +1 -1
- package/scripts/install-release.sh +7 -0
- package/scripts/mock-provider/run-regressions.mjs +60 -14
- package/scripts/pack-mode.mjs +30 -1
- package/scripts/publish-rcc.mjs +31 -0
- package/scripts/tests/apply-patch-loop.mjs +100 -9
- package/scripts/tests/golden-provider-cycle.mjs +12 -1
- package/scripts/tests/responses-provider-dry-run.mjs +15 -1
- package/scripts/tests/virtual-router-health.mjs +12 -5
- package/scripts/tools/capture-provider-goldens.mjs +75 -25
- package/scripts/tools/responses-golden-dry-run.mjs +17 -1
- package/scripts/tools/responses-provider-replay.mjs +17 -1
- package/scripts/tools/sync-ci-goldens.mjs +131 -0
- package/scripts/verification/samples/openai-chat-list-local-files.json +19 -796
- package/scripts/verify-e2e-toolcall.mjs +52 -0
- package/dist/providers/compat/config/index.d.ts +0 -1
- package/dist/providers/compat/config/index.js +0 -5
- package/dist/providers/compat/config/index.js.map +0 -1
- package/dist/providers/compat/iflow/index.d.ts +0 -27
- package/dist/providers/compat/iflow/index.js +0 -32
- package/dist/providers/compat/iflow/index.js.map +0 -1
- package/dist/providers/compat/lmstudio/index.d.ts +0 -4
- package/dist/providers/compat/lmstudio/index.js +0 -10
- package/dist/providers/compat/lmstudio/index.js.map +0 -1
- package/dist/providers/compat/passthrough/index.d.ts +0 -4
- package/dist/providers/compat/passthrough/index.js +0 -9
- package/dist/providers/compat/passthrough/index.js.map +0 -1
- package/dist/providers/compat/responses/index.d.ts +0 -1
- package/dist/providers/compat/responses/index.js +0 -8
- package/dist/providers/compat/responses/index.js.map +0 -1
- package/dist/providers/core/composite/compat/anthropic.d.ts +0 -3
- package/dist/providers/core/composite/compat/anthropic.js +0 -7
- package/dist/providers/core/composite/compat/anthropic.js.map +0 -1
- package/dist/providers/core/composite/compat/gemini.d.ts +0 -3
- package/dist/providers/core/composite/compat/gemini.js +0 -7
- package/dist/providers/core/composite/compat/gemini.js.map +0 -1
- package/dist/providers/core/composite/compat/openai-compat-aggregator.d.ts +0 -9
- package/dist/providers/core/composite/compat/openai-compat-aggregator.js +0 -135
- package/dist/providers/core/composite/compat/openai-compat-aggregator.js.map +0 -1
- package/dist/providers/core/composite/compat/responses.d.ts +0 -3
- package/dist/providers/core/composite/compat/responses.js +0 -91
- package/dist/providers/core/composite/compat/responses.js.map +0 -1
- package/dist/providers/core/composite/provider-composite.d.ts +0 -50
- package/dist/providers/core/composite/provider-composite.js +0 -235
- package/dist/providers/core/composite/provider-composite.js.map +0 -1
- package/scripts/tests/apply-patch-loop.mjs.bak +0 -363
|
@@ -13,21 +13,23 @@ import { HttpClient } from '../utils/http-client.js';
|
|
|
13
13
|
import { DynamicProfileLoader, ServiceProfileValidator } from '../config/service-profiles.js';
|
|
14
14
|
import { ApiKeyAuthProvider } from '../../auth/apikey-auth.js';
|
|
15
15
|
import { OAuthAuthProvider } from '../../auth/oauth-auth.js';
|
|
16
|
+
import { logOAuthDebug } from '../../auth/oauth-logger.js';
|
|
16
17
|
import { TokenFileAuthProvider } from '../../auth/tokenfile-auth.js';
|
|
17
18
|
import { ensureValidOAuthToken, handleUpstreamInvalidOAuthToken } from '../../auth/oauth-lifecycle.js';
|
|
18
|
-
import {
|
|
19
|
-
import { writeProviderSnapshot } from '../utils/snapshot-writer.js';
|
|
20
|
-
import { ProviderComposite } from '../composite/provider-composite.js';
|
|
19
|
+
import { attachProviderSseSnapshotStream, writeProviderSnapshot } from '../utils/snapshot-writer.js';
|
|
21
20
|
import { attachProviderRuntimeMetadata } from './provider-runtime-metadata.js';
|
|
22
21
|
import { OpenAIChatProtocolClient } from '../../../client/openai/chat-protocol-client.js';
|
|
22
|
+
import { HttpRequestExecutor } from './http-request-executor.js';
|
|
23
|
+
import { extractStatusCodeFromError } from './provider-error-classifier.js';
|
|
23
24
|
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
25
|
+
const DEFAULT_USER_AGENT = 'RouteCodex/2.0';
|
|
24
26
|
export class HttpTransportProvider extends BaseProvider {
|
|
25
27
|
type;
|
|
26
28
|
authProvider = null;
|
|
27
29
|
httpClient;
|
|
28
30
|
serviceProfile;
|
|
29
|
-
hookSystemIntegration;
|
|
30
31
|
protocolClient;
|
|
32
|
+
requestExecutor;
|
|
31
33
|
injectedConfig = null;
|
|
32
34
|
constructor(config, dependencies, moduleType, protocolClient) {
|
|
33
35
|
super(config, dependencies);
|
|
@@ -39,10 +41,9 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
39
41
|
this.validateConfig();
|
|
40
42
|
// 创建HTTP客户端
|
|
41
43
|
this.createHttpClient();
|
|
44
|
+
this.requestExecutor = new HttpRequestExecutor(this.httpClient, this.createRequestExecutorDeps());
|
|
42
45
|
// 创建认证提供者
|
|
43
46
|
this.authProvider = this.createAuthProvider();
|
|
44
|
-
// 初始化Hook系统集成
|
|
45
|
-
this.hookSystemIntegration = this.initializeHookSystem();
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
49
|
* 确保认证提供者完成初始化(避免 ApiKeyAuthProvider 未初始化导致的报错)
|
|
@@ -54,12 +55,13 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
54
55
|
const providerConfig = this.config.config;
|
|
55
56
|
const extensions = this.getConfigExtensions();
|
|
56
57
|
const auth = providerConfig.auth;
|
|
57
|
-
|
|
58
|
+
const usesTokenFile = this.authProvider instanceof TokenFileAuthProvider;
|
|
59
|
+
if (this.normalizeAuthMode(auth.type) === 'oauth' && !usesTokenFile) {
|
|
58
60
|
const oauthAuth = auth;
|
|
59
61
|
const oauthProviderId = this.ensureOAuthProviderId(oauthAuth, extensions);
|
|
60
62
|
const forceReauthorize = false;
|
|
61
63
|
const tokenFileHint = oauthAuth.tokenFile ?? '(default)';
|
|
62
|
-
|
|
64
|
+
logOAuthDebug(`[OAuth] [init] provider=${oauthProviderId} type=${auth.type} tokenFile=${tokenFileHint} forceReauth=${forceReauthorize}`);
|
|
63
65
|
this.dependencies.logger?.logModule?.(this.id, 'oauth-init-start', {
|
|
64
66
|
providerType: oauthProviderId,
|
|
65
67
|
tokenFile: tokenFileHint,
|
|
@@ -71,7 +73,7 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
71
73
|
openBrowser: true,
|
|
72
74
|
forceReauthorize
|
|
73
75
|
});
|
|
74
|
-
|
|
76
|
+
logOAuthDebug('[OAuth] [init] ensureValid OK');
|
|
75
77
|
try {
|
|
76
78
|
if (this.authProvider instanceof TokenFileAuthProvider) {
|
|
77
79
|
await this.authProvider.initialize();
|
|
@@ -113,14 +115,6 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
|
-
// 初始化新的Hook系统集成
|
|
117
|
-
await this.hookSystemIntegration.initialize();
|
|
118
|
-
// 设置调试配置(向后兼容)
|
|
119
|
-
this.configureHookDebugging();
|
|
120
|
-
this.dependencies.logger?.logModule(this.id, 'provider-hook-system-initialized', {
|
|
121
|
-
providerType: this.providerType,
|
|
122
|
-
integrationEnabled: true
|
|
123
|
-
});
|
|
124
118
|
}
|
|
125
119
|
catch (error) {
|
|
126
120
|
// 暴露问题,快速失败,便于定位凭证问题
|
|
@@ -150,83 +144,6 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
150
144
|
getConfig() {
|
|
151
145
|
return this.injectedConfig ?? this.config.config ?? null;
|
|
152
146
|
}
|
|
153
|
-
/**
|
|
154
|
-
* 初始化Hook系统集成
|
|
155
|
-
*/
|
|
156
|
-
initializeHookSystem() {
|
|
157
|
-
try {
|
|
158
|
-
const integration = createHookSystemIntegration(this.dependencies, this.id, {
|
|
159
|
-
enabled: true,
|
|
160
|
-
debugMode: true, // Provider v2默认启用调试模式
|
|
161
|
-
snapshotEnabled: true,
|
|
162
|
-
migrationMode: true // 迁移现有Hooks
|
|
163
|
-
});
|
|
164
|
-
this.dependencies.logger?.logModule(this.id, 'hook-system-integration-created', {
|
|
165
|
-
providerId: this.id
|
|
166
|
-
});
|
|
167
|
-
return integration;
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
this.dependencies.logger?.logModule(this.id, 'hook-system-integration-failed', {
|
|
171
|
-
error: error instanceof Error ? error.message : String(error)
|
|
172
|
-
});
|
|
173
|
-
// 创建兼容的空实现,避免系统崩溃
|
|
174
|
-
return {
|
|
175
|
-
getBidirectionalHookManager: () => ({
|
|
176
|
-
registerHook: () => { },
|
|
177
|
-
unregisterHook: () => { },
|
|
178
|
-
executeHookChain: async () => ({ data: {}, metrics: {} }),
|
|
179
|
-
setDebugConfig: () => { }
|
|
180
|
-
}),
|
|
181
|
-
setDebugConfig: () => { },
|
|
182
|
-
initialize: async () => { },
|
|
183
|
-
getStats: () => ({ enabled: false }),
|
|
184
|
-
healthCheck: async () => ({ healthy: true }),
|
|
185
|
-
start: async () => { },
|
|
186
|
-
stop: async () => { },
|
|
187
|
-
shutdown: async () => { }
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* 配置Hook调试(保持向后兼容)
|
|
193
|
-
*/
|
|
194
|
-
configureHookDebugging() {
|
|
195
|
-
try {
|
|
196
|
-
// 设置调试配置(使用统一Hook系统的阶段字符串)
|
|
197
|
-
const debugConfig = {
|
|
198
|
-
enabled: true,
|
|
199
|
-
level: 'verbose',
|
|
200
|
-
maxDataSize: 1024 * 64, // 64KB 单次输出上限,避免过大控制台噪声
|
|
201
|
-
stages: [
|
|
202
|
-
'request_preprocessing',
|
|
203
|
-
'request_validation',
|
|
204
|
-
'authentication',
|
|
205
|
-
'http_request',
|
|
206
|
-
'http_response',
|
|
207
|
-
'response_validation',
|
|
208
|
-
'response_postprocessing',
|
|
209
|
-
'error_handling'
|
|
210
|
-
],
|
|
211
|
-
outputFormat: 'structured',
|
|
212
|
-
outputTargets: ['console'],
|
|
213
|
-
performanceThresholds: {
|
|
214
|
-
maxHookExecutionTime: 500, // 单个Hook 500ms告警
|
|
215
|
-
maxTotalExecutionTime: 5000, // 阶段总时长 5s 告警
|
|
216
|
-
maxDataSize: 1024 * 256 // 256KB 数据告警
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
this.hookSystemIntegration.setDebugConfig(debugConfig);
|
|
220
|
-
this.dependencies.logger?.logModule(this.id, 'provider-debug-hooks-configured', {
|
|
221
|
-
providerType: this.providerType
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
catch (error) {
|
|
225
|
-
this.dependencies.logger?.logModule(this.id, 'provider-debug-hooks-error', {
|
|
226
|
-
error: error instanceof Error ? error.message : String(error)
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
147
|
getServiceProfile() {
|
|
231
148
|
const cfg = this.config.config;
|
|
232
149
|
const profileKey = this.resolveProfileKey(cfg);
|
|
@@ -320,10 +237,16 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
320
237
|
const auth = this.config.config.auth;
|
|
321
238
|
const extensions = this.getConfigExtensions();
|
|
322
239
|
const authMode = this.normalizeAuthMode(auth.type);
|
|
323
|
-
|
|
240
|
+
this.authMode = authMode;
|
|
241
|
+
let providerIdForAuth = authMode === 'oauth'
|
|
324
242
|
? this.ensureOAuthProviderId(auth, extensions)
|
|
325
243
|
: this.providerType;
|
|
326
|
-
|
|
244
|
+
if (this.type === 'gemini-cli-http-provider') {
|
|
245
|
+
providerIdForAuth = 'gemini-cli';
|
|
246
|
+
}
|
|
247
|
+
if (authMode === 'oauth') {
|
|
248
|
+
this.oauthProviderId = providerIdForAuth;
|
|
249
|
+
}
|
|
327
250
|
const validation = ServiceProfileValidator.validateServiceProfile(providerIdForAuth, authMode);
|
|
328
251
|
if (!validation.isValid) {
|
|
329
252
|
throw new Error(`Invalid auth configuration for ${providerIdForAuth}: ${validation.errors.join(', ')}`);
|
|
@@ -334,9 +257,9 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
334
257
|
}
|
|
335
258
|
else if (authMode === 'oauth') {
|
|
336
259
|
const oauthAuth = auth;
|
|
337
|
-
// For providers like Qwen where public OAuth client may not be available,
|
|
338
|
-
// allow reading tokens produced by external login tools (CLIProxyAPI)
|
|
339
|
-
const useTokenFile = (providerIdForAuth === 'qwen' || providerIdForAuth === 'iflow') &&
|
|
260
|
+
// For providers like Qwen/iflow/Gemini CLI where public OAuth client may not be available,
|
|
261
|
+
// allow reading tokens produced by external login tools (CLIProxyAPI) via token file.
|
|
262
|
+
const useTokenFile = (providerIdForAuth === 'qwen' || providerIdForAuth === 'iflow' || providerIdForAuth === 'gemini-cli') &&
|
|
340
263
|
!oauthAuth.clientId &&
|
|
341
264
|
!oauthAuth.tokenUrl &&
|
|
342
265
|
!oauthAuth.deviceCodeUrl;
|
|
@@ -374,6 +297,27 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
374
297
|
}
|
|
375
298
|
});
|
|
376
299
|
}
|
|
300
|
+
createRequestExecutorDeps() {
|
|
301
|
+
return {
|
|
302
|
+
wantsUpstreamSse: this.wantsUpstreamSse.bind(this),
|
|
303
|
+
getEffectiveEndpoint: () => this.getEffectiveEndpoint(),
|
|
304
|
+
resolveRequestEndpoint: this.resolveRequestEndpoint.bind(this),
|
|
305
|
+
buildRequestHeaders: this.buildRequestHeaders.bind(this),
|
|
306
|
+
finalizeRequestHeaders: this.finalizeRequestHeaders.bind(this),
|
|
307
|
+
applyStreamModeHeaders: this.applyStreamModeHeaders.bind(this),
|
|
308
|
+
getEffectiveBaseUrl: () => this.getEffectiveBaseUrl(),
|
|
309
|
+
buildHttpRequestBody: this.buildHttpRequestBody.bind(this),
|
|
310
|
+
prepareSseRequestBody: this.prepareSseRequestBody.bind(this),
|
|
311
|
+
getEntryEndpointFromPayload: this.getEntryEndpointFromPayload.bind(this),
|
|
312
|
+
getClientRequestIdFromContext: this.getClientRequestIdFromContext.bind(this),
|
|
313
|
+
wrapUpstreamSseResponse: this.wrapUpstreamSseResponse.bind(this),
|
|
314
|
+
getHttpRetryLimit: () => this.getHttpRetryLimit(),
|
|
315
|
+
shouldRetryHttpError: this.shouldRetryHttpError.bind(this),
|
|
316
|
+
delayBeforeHttpRetry: this.delayBeforeHttpRetry.bind(this),
|
|
317
|
+
tryRecoverOAuthAndReplay: this.tryRecoverOAuthAndReplay.bind(this),
|
|
318
|
+
normalizeHttpError: this.normalizeHttpError.bind(this)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
377
321
|
async preprocessRequest(request) {
|
|
378
322
|
const context = this.createProviderContext();
|
|
379
323
|
const runtimeMetadata = context.runtimeMetadata;
|
|
@@ -416,89 +360,19 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
416
360
|
}
|
|
417
361
|
}
|
|
418
362
|
catch { /* ignore */ }
|
|
419
|
-
// 获取Hook管理器(新的统一系统)
|
|
420
|
-
const hookManager = this.getHookManager();
|
|
421
|
-
// 🔍 Hook 1: 请求预处理阶段
|
|
422
|
-
const preprocessResult = await hookManager.executeHookChain('request_preprocessing', 'request', processedRequest, context);
|
|
423
|
-
processedRequest = preprocessResult.data;
|
|
424
|
-
ensureRuntimeMetadata(processedRequest);
|
|
425
|
-
// 🔍 Hook 2: 请求验证阶段
|
|
426
|
-
const validationResult = await hookManager.executeHookChain('request_validation', 'request', processedRequest, context);
|
|
427
|
-
processedRequest = validationResult.data;
|
|
428
|
-
ensureRuntimeMetadata(processedRequest);
|
|
429
|
-
// Provider 层不再修改工具 schema;统一入口在 llmswitch-core/兼容层
|
|
430
|
-
// 新增:ProviderComposite.compat.request(协议敏感;Fail Fast)
|
|
431
|
-
try {
|
|
432
|
-
const compatProfile = (runtime?.compatibilityProfile || '').toLowerCase();
|
|
433
|
-
const shouldRunCompat = compatProfile !== 'none';
|
|
434
|
-
if (shouldRunCompat) {
|
|
435
|
-
ensureRuntimeMetadata(processedRequest);
|
|
436
|
-
processedRequest = await ProviderComposite.applyRequest(processedRequest, {
|
|
437
|
-
providerType: runtime?.providerType || this.providerType,
|
|
438
|
-
providerFamily: runtime?.providerFamily || runtime?.providerId || runtime?.providerKey,
|
|
439
|
-
dependencies: this.dependencies
|
|
440
|
-
});
|
|
441
|
-
ensureRuntimeMetadata(processedRequest);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
catch (e) {
|
|
445
|
-
// 暴露问题,不兜底
|
|
446
|
-
this.dependencies.logger?.logModule?.(this.id, 'compat-request-error', {
|
|
447
|
-
error: e instanceof Error ? e.message : String(e)
|
|
448
|
-
});
|
|
449
|
-
throw e;
|
|
450
|
-
}
|
|
451
363
|
return processedRequest;
|
|
452
364
|
}
|
|
453
365
|
async postprocessResponse(response, context) {
|
|
454
366
|
const runtime = this.getRuntimeProfile();
|
|
455
|
-
// 流式短路:若上游仍返回 SSE,则统一包装为 __sse_responses,交由 HTTP 层原样透传
|
|
456
|
-
try {
|
|
457
|
-
const responseRecord = this.asResponseRecord(response);
|
|
458
|
-
if (responseRecord.__sse_stream) {
|
|
459
|
-
return { __sse_responses: responseRecord.__sse_stream };
|
|
460
|
-
}
|
|
461
|
-
if (responseRecord.data?.__sse_stream) {
|
|
462
|
-
return { __sse_responses: responseRecord.data.__sse_stream };
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
catch {
|
|
466
|
-
// ignore
|
|
467
|
-
}
|
|
468
367
|
const processingTime = Date.now() - context.startTime;
|
|
469
368
|
let processedResponse = response;
|
|
470
|
-
// 获取Hook管理器(新的统一系统)
|
|
471
|
-
const hookManager = this.getHookManager();
|
|
472
|
-
// 🔍 Hook 3: HTTP响应阶段
|
|
473
|
-
const httpResponseResult = await hookManager.executeHookChain('http_response', 'response', processedResponse, context);
|
|
474
|
-
processedResponse = httpResponseResult.data;
|
|
475
|
-
// 🔍 Hook 4: 响应验证阶段
|
|
476
|
-
const validationResult = await hookManager.executeHookChain('response_validation', 'response', processedResponse, context);
|
|
477
|
-
processedResponse = validationResult.data;
|
|
478
|
-
// 🔍 Hook 5: 响应后处理阶段
|
|
479
|
-
const postprocessResult = await hookManager.executeHookChain('response_postprocessing', 'response', processedResponse, context);
|
|
480
|
-
processedResponse = postprocessResult.data;
|
|
481
|
-
// 新增:ProviderComposite.compat.response(在封装/模型名还原之前)
|
|
482
|
-
try {
|
|
483
|
-
const compatProfile = (runtime?.compatibilityProfile || '').toLowerCase();
|
|
484
|
-
const shouldRunCompat = compatProfile !== 'none';
|
|
485
|
-
if (shouldRunCompat) {
|
|
486
|
-
processedResponse = await ProviderComposite.applyResponse(processedResponse, undefined, {
|
|
487
|
-
providerType: runtime?.providerType || this.providerType,
|
|
488
|
-
providerFamily: runtime?.providerFamily || runtime?.providerId || runtime?.providerKey,
|
|
489
|
-
dependencies: this.dependencies,
|
|
490
|
-
runtime: context.runtimeMetadata
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
catch (e) {
|
|
495
|
-
this.dependencies.logger?.logModule?.(this.id, 'compat-response-error', {
|
|
496
|
-
error: e instanceof Error ? e.message : String(e)
|
|
497
|
-
});
|
|
498
|
-
throw e;
|
|
499
|
-
}
|
|
500
|
-
const processedRecord = this.asResponseRecord(processedResponse);
|
|
501
369
|
const originalRecord = this.asResponseRecord(response);
|
|
370
|
+
const processedRecord = this.asResponseRecord(processedResponse);
|
|
371
|
+
const sseStream = processedRecord.__sse_responses ||
|
|
372
|
+
processedRecord.data?.__sse_responses;
|
|
373
|
+
if (sseStream) {
|
|
374
|
+
return { __sse_responses: sseStream };
|
|
375
|
+
}
|
|
502
376
|
return {
|
|
503
377
|
data: processedRecord.data || processedResponse,
|
|
504
378
|
status: processedRecord.status ?? originalRecord.status,
|
|
@@ -509,195 +383,13 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
509
383
|
providerType: this.providerType,
|
|
510
384
|
// 对外暴露的 model 统一为入站模型
|
|
511
385
|
model: context.model ?? this.extractModel(processedRecord) ?? this.extractModel(originalRecord),
|
|
512
|
-
usage: this.extractUsage(processedRecord) ?? this.extractUsage(originalRecord)
|
|
513
|
-
hookMetrics: {
|
|
514
|
-
httpResponse: httpResponseResult.metrics,
|
|
515
|
-
validation: validationResult.metrics,
|
|
516
|
-
postprocess: postprocessResult.metrics
|
|
517
|
-
}
|
|
386
|
+
usage: this.extractUsage(processedRecord) ?? this.extractUsage(originalRecord)
|
|
518
387
|
}
|
|
519
388
|
};
|
|
520
389
|
}
|
|
521
390
|
async sendRequestInternal(request) {
|
|
522
391
|
const context = this.createProviderContext();
|
|
523
|
-
|
|
524
|
-
const hookManager = this.getHookManager();
|
|
525
|
-
// 🔍 Hook 8: HTTP请求阶段
|
|
526
|
-
const httpRequestResult = await hookManager.executeHookChain('http_request', 'request', request, context);
|
|
527
|
-
const processedRequest = httpRequestResult.data;
|
|
528
|
-
const wantsSse = this.wantsUpstreamSse(processedRequest, context);
|
|
529
|
-
// 仅传入 endpoint,让 HttpClient 按 baseUrl 进行拼接;避免 full URL 再次拼接导致 /https:/ 重复
|
|
530
|
-
const defaultEndpoint = this.getEffectiveEndpoint();
|
|
531
|
-
const endpoint = this.resolveRequestEndpoint(processedRequest, defaultEndpoint);
|
|
532
|
-
const headers = await this.buildRequestHeaders();
|
|
533
|
-
let finalHeaders = await this.finalizeRequestHeaders(headers, processedRequest);
|
|
534
|
-
finalHeaders = this.applyStreamModeHeaders(finalHeaders, wantsSse);
|
|
535
|
-
const targetUrl = `${this.getEffectiveBaseUrl().replace(/\/$/, '')}/${endpoint.startsWith('/') ? endpoint.slice(1) : endpoint}`;
|
|
536
|
-
// Flatten request body to standard OpenAI Chat JSON
|
|
537
|
-
const finalBody = this.buildHttpRequestBody(processedRequest);
|
|
538
|
-
if (wantsSse) {
|
|
539
|
-
this.prepareSseRequestBody(finalBody, context);
|
|
540
|
-
}
|
|
541
|
-
const entryEndpoint = this.getEntryEndpointFromPayload(processedRequest);
|
|
542
|
-
const clientRequestId = this.getClientRequestIdFromContext(context);
|
|
543
|
-
// 快照:provider-request(默认开启,脱敏headers)
|
|
544
|
-
try {
|
|
545
|
-
await writeProviderSnapshot({
|
|
546
|
-
phase: 'provider-request',
|
|
547
|
-
requestId: context.requestId,
|
|
548
|
-
data: finalBody,
|
|
549
|
-
headers: finalHeaders,
|
|
550
|
-
url: targetUrl,
|
|
551
|
-
entryEndpoint,
|
|
552
|
-
clientRequestId
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
catch { /* non-blocking */ }
|
|
556
|
-
// 发送HTTP请求(根据是否需要 SSE 决定传输模式)
|
|
557
|
-
let response;
|
|
558
|
-
try {
|
|
559
|
-
if (wantsSse) {
|
|
560
|
-
const stream = await this.httpClient.postStream(endpoint, finalBody, finalHeaders);
|
|
561
|
-
response = await this.wrapUpstreamSseResponse(stream, context);
|
|
562
|
-
try {
|
|
563
|
-
await writeProviderSnapshot({
|
|
564
|
-
phase: 'provider-response',
|
|
565
|
-
requestId: context.requestId,
|
|
566
|
-
data: { mode: 'sse' },
|
|
567
|
-
headers: finalHeaders,
|
|
568
|
-
url: targetUrl,
|
|
569
|
-
entryEndpoint,
|
|
570
|
-
clientRequestId
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
catch { /* non-blocking */ }
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
response = await this.httpClient.post(endpoint, finalBody, finalHeaders);
|
|
577
|
-
try {
|
|
578
|
-
await writeProviderSnapshot({
|
|
579
|
-
phase: 'provider-response',
|
|
580
|
-
requestId: context.requestId,
|
|
581
|
-
data: response,
|
|
582
|
-
headers: finalHeaders,
|
|
583
|
-
url: targetUrl,
|
|
584
|
-
entryEndpoint,
|
|
585
|
-
clientRequestId
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
catch { /* non-blocking */ }
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
catch (error) {
|
|
592
|
-
// OAuth token 失效:尝试刷新/重获并重试一次
|
|
593
|
-
try {
|
|
594
|
-
const providerAuth = this.config.config.auth;
|
|
595
|
-
if (this.normalizeAuthMode(providerAuth.type) === 'oauth') {
|
|
596
|
-
const shouldRetry = await handleUpstreamInvalidOAuthToken(this.providerType, providerAuth, error);
|
|
597
|
-
if (shouldRetry) {
|
|
598
|
-
const retryHeaders = await this.buildRequestHeaders();
|
|
599
|
-
let finalRetryHeaders = await this.finalizeRequestHeaders(retryHeaders, processedRequest);
|
|
600
|
-
finalRetryHeaders = this.applyStreamModeHeaders(finalRetryHeaders, wantsSse);
|
|
601
|
-
if (wantsSse) {
|
|
602
|
-
const stream = await this.httpClient.postStream(endpoint, finalBody, finalRetryHeaders);
|
|
603
|
-
const wrapped = await this.wrapUpstreamSseResponse(stream, context);
|
|
604
|
-
try {
|
|
605
|
-
await writeProviderSnapshot({
|
|
606
|
-
phase: 'provider-response',
|
|
607
|
-
requestId: context.requestId,
|
|
608
|
-
data: { mode: 'sse', retry: true },
|
|
609
|
-
headers: finalRetryHeaders,
|
|
610
|
-
url: targetUrl,
|
|
611
|
-
entryEndpoint,
|
|
612
|
-
clientRequestId
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
catch { /* non-blocking */ }
|
|
616
|
-
return wrapped;
|
|
617
|
-
}
|
|
618
|
-
response = await this.httpClient.post(endpoint, finalBody, finalRetryHeaders);
|
|
619
|
-
try {
|
|
620
|
-
await writeProviderSnapshot({
|
|
621
|
-
phase: 'provider-response',
|
|
622
|
-
requestId: context.requestId,
|
|
623
|
-
data: response,
|
|
624
|
-
headers: finalRetryHeaders,
|
|
625
|
-
url: targetUrl,
|
|
626
|
-
entryEndpoint,
|
|
627
|
-
clientRequestId
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
catch { /* non-blocking */ }
|
|
631
|
-
return response;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
catch { /* ignore and fallthrough */ }
|
|
636
|
-
// 🔍 Hook 9: 错误处理阶段
|
|
637
|
-
const errorResult = await hookManager.executeHookChain('error_handling', 'error', { error, request: processedRequest, url: targetUrl, headers: finalHeaders }, context);
|
|
638
|
-
// 如果Hook处理了错误,使用Hook的返回结果
|
|
639
|
-
const hookErrorData = errorResult.data;
|
|
640
|
-
if (hookErrorData && hookErrorData.error === false) {
|
|
641
|
-
return hookErrorData;
|
|
642
|
-
}
|
|
643
|
-
// 规范化错误:补充结构化字段,移除仅文本填充的旧做法
|
|
644
|
-
const normalized = error;
|
|
645
|
-
try {
|
|
646
|
-
// 提取状态码
|
|
647
|
-
const msg = typeof normalized.message === 'string' ? normalized.message : String(normalized || '');
|
|
648
|
-
const m = msg.match(/HTTP\s+(\d{3})/i);
|
|
649
|
-
const parsedStatus = m ? parseInt(m[1], 10) : undefined;
|
|
650
|
-
const statusCode = Number.isFinite(normalized.statusCode)
|
|
651
|
-
? Number(normalized.statusCode)
|
|
652
|
-
: (Number.isFinite(normalized.status) ? Number(normalized.status) : (parsedStatus || undefined));
|
|
653
|
-
if (statusCode && !Number.isNaN(statusCode)) {
|
|
654
|
-
normalized.statusCode = statusCode;
|
|
655
|
-
if (!normalized.status) {
|
|
656
|
-
normalized.status = statusCode;
|
|
657
|
-
}
|
|
658
|
-
if (!normalized.code) {
|
|
659
|
-
normalized.code = `HTTP_${statusCode}`;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
// 兼容 Manager 的 code 路径(response.data.error.code)
|
|
663
|
-
if (!normalized.response) {
|
|
664
|
-
normalized.response = {};
|
|
665
|
-
}
|
|
666
|
-
if (!normalized.response.data) {
|
|
667
|
-
normalized.response.data = {};
|
|
668
|
-
}
|
|
669
|
-
if (!normalized.response.data.error) {
|
|
670
|
-
normalized.response.data.error = {};
|
|
671
|
-
}
|
|
672
|
-
if (normalized.code && !normalized.response.data.error.code) {
|
|
673
|
-
normalized.response.data.error.code = normalized.code;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
catch { /* keep original */ }
|
|
677
|
-
// 快照:provider-error(结构化写入)
|
|
678
|
-
try {
|
|
679
|
-
await writeProviderSnapshot({
|
|
680
|
-
phase: 'provider-error',
|
|
681
|
-
requestId: context.requestId,
|
|
682
|
-
data: {
|
|
683
|
-
status: normalized?.statusCode ?? normalized?.status ?? null,
|
|
684
|
-
code: normalized?.code ?? null,
|
|
685
|
-
error: typeof normalized?.message === 'string' ? normalized.message : String(normalized || '')
|
|
686
|
-
},
|
|
687
|
-
headers: finalHeaders,
|
|
688
|
-
url: targetUrl,
|
|
689
|
-
entryEndpoint,
|
|
690
|
-
clientRequestId
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
catch { /* non-blocking */ }
|
|
694
|
-
throw normalized;
|
|
695
|
-
}
|
|
696
|
-
// Provider 不处理工具修复/注入逻辑:统一收敛到 llmswitch-core 与兼容层
|
|
697
|
-
// 此处不做任何自动修复/重试,保持单次请求的幂等与可观测性
|
|
698
|
-
try { /* no-op */ }
|
|
699
|
-
catch { /* ignore */ }
|
|
700
|
-
return response;
|
|
392
|
+
return this.requestExecutor.execute(request, context);
|
|
701
393
|
}
|
|
702
394
|
wantsUpstreamSse(_request, _context) {
|
|
703
395
|
return false;
|
|
@@ -733,6 +425,135 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
733
425
|
return false;
|
|
734
426
|
}
|
|
735
427
|
}
|
|
428
|
+
getHttpRetryLimit() {
|
|
429
|
+
return 3;
|
|
430
|
+
}
|
|
431
|
+
async delayBeforeHttpRetry(attempt) {
|
|
432
|
+
const delay = Math.min(500 * attempt, 2000);
|
|
433
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
434
|
+
}
|
|
435
|
+
shouldRetryHttpError(error, attempt, maxAttempts) {
|
|
436
|
+
if (attempt >= maxAttempts) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
const normalized = error;
|
|
440
|
+
const statusCode = extractStatusCodeFromError(normalized);
|
|
441
|
+
if (statusCode && statusCode >= 500) {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
async tryRecoverOAuthAndReplay(error, requestInfo, processedRequest, captureSse, context) {
|
|
447
|
+
try {
|
|
448
|
+
const providerAuth = this.config.config.auth;
|
|
449
|
+
if (this.normalizeAuthMode(providerAuth.type) !== 'oauth') {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
const shouldRetry = await handleUpstreamInvalidOAuthToken(this.oauthProviderId || this.providerType, providerAuth, error);
|
|
453
|
+
if (!shouldRetry) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
const retryHeaders = await this.buildRequestHeaders();
|
|
457
|
+
let finalRetryHeaders = await this.finalizeRequestHeaders(retryHeaders, processedRequest);
|
|
458
|
+
finalRetryHeaders = this.applyStreamModeHeaders(finalRetryHeaders, requestInfo.wantsSse);
|
|
459
|
+
if (requestInfo.wantsSse) {
|
|
460
|
+
const upstreamStream = await this.httpClient.postStream(requestInfo.endpoint, requestInfo.body, finalRetryHeaders);
|
|
461
|
+
const streamForHost = captureSse
|
|
462
|
+
? attachProviderSseSnapshotStream(upstreamStream, {
|
|
463
|
+
requestId: context.requestId,
|
|
464
|
+
headers: finalRetryHeaders,
|
|
465
|
+
url: requestInfo.targetUrl,
|
|
466
|
+
entryEndpoint: requestInfo.entryEndpoint,
|
|
467
|
+
clientRequestId: requestInfo.clientRequestId,
|
|
468
|
+
extra: { retry: true }
|
|
469
|
+
})
|
|
470
|
+
: upstreamStream;
|
|
471
|
+
const wrapped = await this.wrapUpstreamSseResponse(streamForHost, context);
|
|
472
|
+
if (!captureSse) {
|
|
473
|
+
try {
|
|
474
|
+
await writeProviderSnapshot({
|
|
475
|
+
phase: 'provider-response',
|
|
476
|
+
requestId: context.requestId,
|
|
477
|
+
data: { mode: 'sse', retry: true },
|
|
478
|
+
headers: finalRetryHeaders,
|
|
479
|
+
url: requestInfo.targetUrl,
|
|
480
|
+
entryEndpoint: requestInfo.entryEndpoint,
|
|
481
|
+
clientRequestId: requestInfo.clientRequestId
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
catch { /* non-blocking */ }
|
|
485
|
+
}
|
|
486
|
+
return wrapped;
|
|
487
|
+
}
|
|
488
|
+
const response = await this.httpClient.post(requestInfo.endpoint, requestInfo.body, finalRetryHeaders);
|
|
489
|
+
try {
|
|
490
|
+
await writeProviderSnapshot({
|
|
491
|
+
phase: 'provider-response',
|
|
492
|
+
requestId: context.requestId,
|
|
493
|
+
data: response,
|
|
494
|
+
headers: finalRetryHeaders,
|
|
495
|
+
url: requestInfo.targetUrl,
|
|
496
|
+
entryEndpoint: requestInfo.entryEndpoint,
|
|
497
|
+
clientRequestId: requestInfo.clientRequestId
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
catch { /* non-blocking */ }
|
|
501
|
+
return response;
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async normalizeHttpError(error, processedRequest, requestInfo, context) {
|
|
508
|
+
const normalized = error;
|
|
509
|
+
try {
|
|
510
|
+
const statusCode = extractStatusCodeFromError(normalized);
|
|
511
|
+
if (statusCode && !Number.isNaN(statusCode)) {
|
|
512
|
+
normalized.statusCode = statusCode;
|
|
513
|
+
if (!normalized.status) {
|
|
514
|
+
normalized.status = statusCode;
|
|
515
|
+
}
|
|
516
|
+
if (!normalized.code) {
|
|
517
|
+
normalized.code = `HTTP_${statusCode}`;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (!normalized.response) {
|
|
521
|
+
normalized.response = {};
|
|
522
|
+
}
|
|
523
|
+
if (!normalized.response.data) {
|
|
524
|
+
normalized.response.data = {};
|
|
525
|
+
}
|
|
526
|
+
if (!normalized.response.data.error) {
|
|
527
|
+
normalized.response.data.error = {};
|
|
528
|
+
}
|
|
529
|
+
if (normalized.code && !normalized.response.data.error.code) {
|
|
530
|
+
normalized.response.data.error.code = normalized.code;
|
|
531
|
+
}
|
|
532
|
+
if (normalized.message && !normalized.response.data.error.message) {
|
|
533
|
+
normalized.response.data.error.message = normalized.message;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
/* ignore */
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
await writeProviderSnapshot({
|
|
541
|
+
phase: 'provider-error',
|
|
542
|
+
requestId: context.requestId,
|
|
543
|
+
data: {
|
|
544
|
+
status: normalized?.statusCode ?? normalized?.status ?? null,
|
|
545
|
+
code: normalized?.code ?? null,
|
|
546
|
+
error: typeof normalized?.message === 'string' ? normalized.message : String(error || '')
|
|
547
|
+
},
|
|
548
|
+
headers: requestInfo.headers,
|
|
549
|
+
url: requestInfo.targetUrl,
|
|
550
|
+
entryEndpoint: requestInfo.entryEndpoint ?? this.getEntryEndpointFromPayload(processedRequest),
|
|
551
|
+
clientRequestId: requestInfo.clientRequestId ?? this.getClientRequestIdFromContext(context)
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
catch { /* non-blocking */ }
|
|
555
|
+
return normalized;
|
|
556
|
+
}
|
|
736
557
|
/**
|
|
737
558
|
* 为特定请求确定最终 endpoint(默认使用配置值,可由子类覆写)
|
|
738
559
|
*/
|
|
@@ -774,6 +595,14 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
774
595
|
const baseHeaders = {
|
|
775
596
|
'Content-Type': 'application/json'
|
|
776
597
|
};
|
|
598
|
+
const runtimeMetadata = this.getCurrentRuntimeMetadata();
|
|
599
|
+
const inboundMetadata = runtimeMetadata?.metadata;
|
|
600
|
+
const inboundUserAgent = typeof inboundMetadata?.userAgent === 'string' && inboundMetadata.userAgent.trim()
|
|
601
|
+
? inboundMetadata.userAgent.trim()
|
|
602
|
+
: undefined;
|
|
603
|
+
const inboundOriginator = typeof inboundMetadata?.clientOriginator === 'string' && inboundMetadata.clientOriginator.trim()
|
|
604
|
+
? inboundMetadata.clientOriginator.trim()
|
|
605
|
+
: undefined;
|
|
777
606
|
// 服务特定头部
|
|
778
607
|
const serviceHeaders = this.serviceProfile.headers || {};
|
|
779
608
|
// 配置覆盖头部
|
|
@@ -785,14 +614,14 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
785
614
|
if (this.normalizeAuthMode(auth.type) === 'oauth') {
|
|
786
615
|
const oauthAuth = auth;
|
|
787
616
|
const oauthProviderId = this.ensureOAuthProviderId(oauthAuth);
|
|
788
|
-
|
|
617
|
+
logOAuthDebug('[OAuth] [headers] ensureValid start (openBrowser=true, forceReauth=false)');
|
|
789
618
|
try {
|
|
790
619
|
await ensureValidOAuthToken(oauthProviderId, oauthAuth, {
|
|
791
620
|
forceReacquireIfRefreshFails: true,
|
|
792
621
|
openBrowser: true,
|
|
793
622
|
forceReauthorize: false
|
|
794
623
|
});
|
|
795
|
-
|
|
624
|
+
logOAuthDebug('[OAuth] [headers] ensureValid OK');
|
|
796
625
|
}
|
|
797
626
|
catch (error) {
|
|
798
627
|
const err = error;
|
|
@@ -833,13 +662,42 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
833
662
|
if (!('Accept' in finalHeaders) && !('accept' in finalHeaders)) {
|
|
834
663
|
finalHeaders['Accept'] = 'application/json';
|
|
835
664
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
665
|
+
const getHeader = (headers, target) => {
|
|
666
|
+
const lowered = target.toLowerCase();
|
|
667
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
668
|
+
if (key.toLowerCase() === lowered && typeof value === 'string' && value.trim()) {
|
|
669
|
+
return value.trim();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return undefined;
|
|
673
|
+
};
|
|
674
|
+
const setHeader = (headers, target, value) => {
|
|
675
|
+
if (!value || !value.trim())
|
|
676
|
+
return;
|
|
677
|
+
const lowered = target.toLowerCase();
|
|
678
|
+
for (const key of Object.keys(headers)) {
|
|
679
|
+
if (key.toLowerCase() === lowered) {
|
|
680
|
+
headers[key] = value;
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
headers[target] = value;
|
|
685
|
+
};
|
|
686
|
+
// Header priority:
|
|
687
|
+
// - user/provider config (overrides/runtime) wins
|
|
688
|
+
// - otherwise inherit from inbound client headers
|
|
689
|
+
// - otherwise fall back to defaults
|
|
690
|
+
const uaFromConfig = getHeader({ ...overrideHeaders, ...runtimeHeaders }, 'User-Agent');
|
|
691
|
+
const uaFromService = getHeader(serviceHeaders, 'User-Agent');
|
|
692
|
+
const resolvedUa = uaFromConfig ?? inboundUserAgent ?? uaFromService ?? DEFAULT_USER_AGENT;
|
|
693
|
+
setHeader(finalHeaders, 'User-Agent', resolvedUa);
|
|
694
|
+
// originator: do not invent one; only forward from config or inbound client
|
|
695
|
+
const originatorFromConfig = getHeader({ ...overrideHeaders, ...runtimeHeaders }, 'originator');
|
|
696
|
+
const originatorFromService = getHeader(serviceHeaders, 'originator');
|
|
697
|
+
const resolvedOriginator = originatorFromConfig ?? inboundOriginator ?? originatorFromService;
|
|
698
|
+
if (resolvedOriginator) {
|
|
699
|
+
setHeader(finalHeaders, 'originator', resolvedOriginator);
|
|
700
|
+
}
|
|
843
701
|
return finalHeaders;
|
|
844
702
|
}
|
|
845
703
|
getEffectiveBaseUrl() {
|
|
@@ -879,9 +737,6 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
879
737
|
const trimmed = value.trim();
|
|
880
738
|
return /^https?:\/\//i.test(trimmed) || trimmed.startsWith('//');
|
|
881
739
|
}
|
|
882
|
-
getHookManager() {
|
|
883
|
-
return this.hookSystemIntegration.getBidirectionalHookManager();
|
|
884
|
-
}
|
|
885
740
|
// (工具自动修复辅助函数已删除)
|
|
886
741
|
getConfigExtensions() {
|
|
887
742
|
const extensions = this.config.config.extensions;
|