@jsonstudio/rcc 0.89.333 → 0.89.524
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/dist/build-info.js +3 -3
- package/dist/build-info.js.map +1 -1
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/token-daemon.d.ts +2 -0
- package/dist/commands/token-daemon.js +183 -0
- package/dist/commands/token-daemon.js.map +1 -0
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/llmswitch/bridge.d.ts +1 -1
- package/dist/modules/llmswitch/bridge.js +3 -2
- package/dist/modules/llmswitch/bridge.js.map +1 -1
- package/dist/modules/pipeline/utils/colored-logger.js +3 -1
- package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
- package/dist/providers/auth/gemini-cli-userinfo-helper.js +12 -2
- package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle.js +261 -22
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/core/config/oauth-flows.d.ts +23 -0
- package/dist/providers/core/config/oauth-flows.js +92 -5
- package/dist/providers/core/config/oauth-flows.js.map +1 -1
- package/dist/providers/core/config/provider-oauth-configs.js +9 -3
- package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +18 -10
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +87 -20
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
- package/dist/providers/core/runtime/http-request-executor.js +41 -1
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +2 -0
- package/dist/providers/core/runtime/http-transport-provider.js +37 -2
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/responses-provider.js +8 -3
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/runtime/vision-debug-utils.d.ts +13 -0
- package/dist/providers/core/runtime/vision-debug-utils.js +114 -0
- package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -0
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +75 -26
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +2 -1
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.d.ts +1 -1
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/server/handlers/sse-dispatcher.js +22 -2
- package/dist/server/handlers/sse-dispatcher.js.map +1 -1
- package/dist/server/runtime/http-server/index.d.ts +9 -0
- package/dist/server/runtime/http-server/index.js +512 -144
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.d.ts +10 -0
- package/dist/server/runtime/http-server/request-executor.js +553 -159
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +5 -0
- package/dist/server/runtime/http-server/routes.js +69 -0
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/runtime-manager.js +18 -0
- package/dist/server/runtime/http-server/runtime-manager.js.map +1 -1
- package/dist/server/utils/utf8-chunk-buffer.d.ts +43 -0
- package/dist/server/utils/utf8-chunk-buffer.js +132 -0
- package/dist/server/utils/utf8-chunk-buffer.js.map +1 -0
- package/dist/token-daemon/index.d.ts +7 -0
- package/dist/token-daemon/index.js +242 -0
- package/dist/token-daemon/index.js.map +1 -0
- package/dist/token-daemon/server-utils.d.ts +33 -0
- package/dist/token-daemon/server-utils.js +155 -0
- package/dist/token-daemon/server-utils.js.map +1 -0
- package/dist/token-daemon/token-daemon.d.ts +20 -0
- package/dist/token-daemon/token-daemon.js +144 -0
- package/dist/token-daemon/token-daemon.js.map +1 -0
- package/dist/token-daemon/token-types.d.ts +44 -0
- package/dist/token-daemon/token-types.js +18 -0
- package/dist/token-daemon/token-types.js.map +1 -0
- package/dist/token-daemon/token-utils.d.ts +17 -0
- package/dist/token-daemon/token-utils.js +153 -0
- package/dist/token-daemon/token-utils.js.map +1 -0
- package/dist/tools/semantic-replay.js +7 -6
- package/dist/tools/semantic-replay.js.map +1 -1
- package/dist/utils/error-handler-registry.d.ts +36 -0
- package/dist/utils/error-handler-registry.js +93 -7
- package/dist/utils/error-handler-registry.js.map +1 -1
- package/node_modules/@jsonstudio/llms/README.md +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/codecs/gemini-openai-codec.js +137 -5
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.js +68 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.js +83 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.js +63 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/universal-shape-filter.js +11 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-gemini.json +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +190 -181
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +195 -195
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +20 -20
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +1 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +24 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +39 -4
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/target-utils.js +6 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +213 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.d.ts +34 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +84 -24
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.js +383 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/gemini-mapper.js +241 -14
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/standardized-bridge.js +14 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/types/standardized.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +82 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils.js +92 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/bridge-message-utils.js +137 -10
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-output-builder.js +43 -2
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/snapshot-utils.js +17 -47
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +25 -2
- package/node_modules/@jsonstudio/llms/dist/index.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/index.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +308 -43
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +11 -17
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.d.ts +0 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.js +0 -12
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +17 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +332 -95
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +1 -1
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.js +36 -24
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/provider-registry.js +2 -1
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +14 -3
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +66 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -1
- package/node_modules/@jsonstudio/llms/dist/servertool/engine.d.ts +27 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +60 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.d.ts +40 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +194 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.js +638 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.d.ts +33 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/registry.d.ts +18 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/registry.js +27 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +208 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +88 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.js +185 -0
- package/node_modules/@jsonstudio/llms/dist/sse/json-to-sse/event-generators/responses.js +15 -3
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +6 -3
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
- package/node_modules/@jsonstudio/llms/dist/sse/types/gemini-types.d.ts +20 -1
- package/node_modules/@jsonstudio/llms/dist/sse/types/responses-types.js +1 -1
- package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.d.ts +73 -0
- package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.js +280 -0
- package/node_modules/@jsonstudio/llms/package.json +1 -1
- package/package.json +2 -2
- package/scripts/pack-mode.mjs +2 -1
- package/scripts/publish-rcc.mjs +20 -4
- package/scripts/tests/virtual-router-health.mjs +141 -6
- package/dist/tools/replay-request.d.ts +0 -0
- package/dist/tools/replay-request.js +0 -2
- package/dist/tools/replay-request.js.map +0 -1
|
@@ -18,7 +18,7 @@ import { buildProviderProfiles } from '../../../providers/profile/provider-profi
|
|
|
18
18
|
import { emitProviderError } from '../../../providers/core/utils/provider-error-reporter.js';
|
|
19
19
|
import { isStageLoggingEnabled, logPipelineStage } from '../../utils/stage-logger.js';
|
|
20
20
|
import { registerDefaultMiddleware } from './middleware.js';
|
|
21
|
-
import { registerHttpRoutes } from './routes.js';
|
|
21
|
+
import { registerHttpRoutes, registerOAuthPortalRoute } from './routes.js';
|
|
22
22
|
import { mapProviderProtocol, normalizeProviderType, resolveProviderIdentity, asRecord } from './provider-utils.js';
|
|
23
23
|
import { resolveRepoRoot, loadLlmswitchModule } from './llmswitch-loader.js';
|
|
24
24
|
import { importCoreModule } from '../../../modules/llmswitch/core-loader.js';
|
|
@@ -98,6 +98,11 @@ export class RouteCodexHttpServer {
|
|
|
98
98
|
console.warn('[RouteCodexHttpServer] Failed to initialize PipelineDebugLogger; falling back to noop logger.', error);
|
|
99
99
|
this.pipelineLogger = createNoopPipelineLogger();
|
|
100
100
|
}
|
|
101
|
+
// Register critical routes early (before provider initialization)
|
|
102
|
+
// This ensures OAuth Portal is available when providers check token validity
|
|
103
|
+
registerDefaultMiddleware(this.app);
|
|
104
|
+
registerOAuthPortalRoute(this.app);
|
|
105
|
+
console.log('[RouteCodexHttpServer] OAuth Portal route registered (early initialization)');
|
|
101
106
|
console.log('[RouteCodexHttpServer] Initialized (pipeline=hub)');
|
|
102
107
|
}
|
|
103
108
|
resolveVirtualRouterInput(userConfig) {
|
|
@@ -334,7 +339,8 @@ export class RouteCodexHttpServer {
|
|
|
334
339
|
// 初始化错误处理
|
|
335
340
|
await this.errorHandling.initialize();
|
|
336
341
|
await this.initializeRouteErrorHub();
|
|
337
|
-
registerDefaultMiddleware
|
|
342
|
+
// registerDefaultMiddleware and registerOAuthPortalRoute already called in constructor
|
|
343
|
+
// Register remaining HTTP routes
|
|
338
344
|
registerHttpRoutes({
|
|
339
345
|
app: this.app,
|
|
340
346
|
config: this.config,
|
|
@@ -624,14 +630,14 @@ export class RouteCodexHttpServer {
|
|
|
624
630
|
if (!this.isPipelineReady()) {
|
|
625
631
|
throw new Error('Hub pipeline runtime is not initialized');
|
|
626
632
|
}
|
|
627
|
-
const
|
|
633
|
+
const initialMetadata = this.buildRequestMetadata(input);
|
|
628
634
|
const providerRequestId = input.requestId;
|
|
629
|
-
const clientRequestId = typeof
|
|
630
|
-
?
|
|
635
|
+
const clientRequestId = typeof initialMetadata.clientRequestId === 'string' && initialMetadata.clientRequestId.trim()
|
|
636
|
+
? initialMetadata.clientRequestId.trim()
|
|
631
637
|
: providerRequestId;
|
|
632
638
|
this.logStage('request.received', providerRequestId, {
|
|
633
639
|
endpoint: input.entryEndpoint,
|
|
634
|
-
stream:
|
|
640
|
+
stream: initialMetadata.stream === true
|
|
635
641
|
});
|
|
636
642
|
try {
|
|
637
643
|
const headerUa = (typeof input.headers?.['user-agent'] === 'string' && input.headers['user-agent']) ||
|
|
@@ -644,7 +650,7 @@ export class RouteCodexHttpServer {
|
|
|
644
650
|
headers: asRecord(input.headers),
|
|
645
651
|
body: input.body,
|
|
646
652
|
metadata: {
|
|
647
|
-
...
|
|
653
|
+
...initialMetadata,
|
|
648
654
|
userAgent: headerUa,
|
|
649
655
|
clientOriginator: headerOriginator
|
|
650
656
|
}
|
|
@@ -654,155 +660,161 @@ export class RouteCodexHttpServer {
|
|
|
654
660
|
// snapshot failure should not block request path
|
|
655
661
|
}
|
|
656
662
|
const pipelineLabel = 'hub';
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const pipelineMetadata = pipelineResult.metadata ?? {};
|
|
664
|
-
const mergedMetadata = { ...metadata, ...pipelineMetadata };
|
|
665
|
-
this.logStage(`${pipelineLabel}.completed`, providerRequestId, {
|
|
666
|
-
route: pipelineResult.routingDecision?.routeName,
|
|
667
|
-
target: pipelineResult.target?.providerKey
|
|
668
|
-
});
|
|
669
|
-
const providerPayload = pipelineResult.providerPayload;
|
|
670
|
-
const target = pipelineResult.target;
|
|
671
|
-
if (!providerPayload || !target?.providerKey) {
|
|
672
|
-
throw Object.assign(new Error('Virtual router did not produce a provider target'), {
|
|
673
|
-
code: 'ERR_NO_PROVIDER_TARGET',
|
|
674
|
-
requestId: input.requestId
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
const runtimeKey = target.runtimeKey || this.providerKeyToRuntimeKey.get(target.providerKey);
|
|
678
|
-
if (!runtimeKey) {
|
|
679
|
-
throw Object.assign(new Error(`Runtime for provider ${target.providerKey} not initialized`), {
|
|
680
|
-
code: 'ERR_RUNTIME_NOT_FOUND',
|
|
681
|
-
requestId: input.requestId
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
const handle = this.providerHandles.get(runtimeKey);
|
|
685
|
-
if (!handle) {
|
|
686
|
-
throw Object.assign(new Error(`Provider runtime ${runtimeKey} not found`), {
|
|
687
|
-
code: 'ERR_PROVIDER_NOT_FOUND',
|
|
688
|
-
requestId: input.requestId
|
|
663
|
+
let iterationMetadata = initialMetadata;
|
|
664
|
+
let followupTriggered = false;
|
|
665
|
+
while (true) {
|
|
666
|
+
this.logStage(`${pipelineLabel}.start`, providerRequestId, {
|
|
667
|
+
endpoint: input.entryEndpoint,
|
|
668
|
+
stream: iterationMetadata.stream
|
|
689
669
|
});
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
(typeof metadataModel === 'string' ? metadataModel : undefined);
|
|
698
|
-
const providerIdToken = target.providerKey || handle.providerId || runtimeKey;
|
|
699
|
-
if (!providerIdToken) {
|
|
700
|
-
throw Object.assign(new Error('Provider identifier missing for request'), {
|
|
701
|
-
code: 'ERR_PROVIDER_ID_MISSING',
|
|
702
|
-
requestId: providerRequestId
|
|
670
|
+
const originalRequestSnapshot = this.cloneRequestPayload(input.body);
|
|
671
|
+
const pipelineResult = await this.runHubPipeline(input, iterationMetadata);
|
|
672
|
+
const pipelineMetadata = pipelineResult.metadata ?? {};
|
|
673
|
+
const mergedMetadata = { ...iterationMetadata, ...pipelineMetadata };
|
|
674
|
+
this.logStage(`${pipelineLabel}.completed`, providerRequestId, {
|
|
675
|
+
route: pipelineResult.routingDecision?.routeName,
|
|
676
|
+
target: pipelineResult.target?.providerKey
|
|
703
677
|
});
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
try {
|
|
712
|
-
await rebindResponsesConversationRequestId(pipelineResult.requestId, enhancedRequestId);
|
|
678
|
+
const providerPayload = pipelineResult.providerPayload;
|
|
679
|
+
const target = pipelineResult.target;
|
|
680
|
+
if (!providerPayload || !target?.providerKey) {
|
|
681
|
+
throw Object.assign(new Error('Virtual router did not produce a provider target'), {
|
|
682
|
+
code: 'ERR_NO_PROVIDER_TARGET',
|
|
683
|
+
requestId: input.requestId
|
|
684
|
+
});
|
|
713
685
|
}
|
|
714
|
-
|
|
715
|
-
|
|
686
|
+
const runtimeKey = target.runtimeKey || this.providerKeyToRuntimeKey.get(target.providerKey);
|
|
687
|
+
if (!runtimeKey) {
|
|
688
|
+
throw Object.assign(new Error(`Runtime for provider ${target.providerKey} not initialized`), {
|
|
689
|
+
code: 'ERR_RUNTIME_NOT_FOUND',
|
|
690
|
+
requestId: input.requestId
|
|
691
|
+
});
|
|
716
692
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
providerProtocol,
|
|
740
|
-
pipelineId: target.providerKey,
|
|
741
|
-
routeName: pipelineResult.routingDecision?.routeName,
|
|
742
|
-
runtimeKey,
|
|
743
|
-
target,
|
|
744
|
-
metadata: mergedMetadata
|
|
745
|
-
});
|
|
746
|
-
this.logStage('provider.send.start', input.requestId, {
|
|
747
|
-
providerKey: target.providerKey,
|
|
748
|
-
runtimeKey,
|
|
749
|
-
protocol: providerProtocol,
|
|
750
|
-
providerType: handle.providerType,
|
|
751
|
-
providerFamily: handle.providerFamily,
|
|
752
|
-
model: providerModel,
|
|
753
|
-
providerLabel
|
|
754
|
-
});
|
|
755
|
-
try {
|
|
756
|
-
const providerResponse = await handle.instance.processIncoming(providerPayload);
|
|
757
|
-
const responseStatus = this.extractResponseStatus(providerResponse);
|
|
758
|
-
this.logStage('provider.send.completed', input.requestId, {
|
|
759
|
-
providerKey: target.providerKey,
|
|
760
|
-
status: responseStatus,
|
|
761
|
-
providerType: handle.providerType,
|
|
762
|
-
providerFamily: handle.providerFamily,
|
|
763
|
-
model: providerModel,
|
|
764
|
-
providerLabel
|
|
765
|
-
});
|
|
766
|
-
const normalized = this.normalizeProviderResponse(providerResponse);
|
|
767
|
-
return await this.convertProviderResponseIfNeeded({
|
|
693
|
+
const handle = this.providerHandles.get(runtimeKey);
|
|
694
|
+
if (!handle) {
|
|
695
|
+
throw Object.assign(new Error(`Provider runtime ${runtimeKey} not found`), {
|
|
696
|
+
code: 'ERR_PROVIDER_NOT_FOUND',
|
|
697
|
+
requestId: input.requestId
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
const providerProtocol = target.outboundProfile ||
|
|
701
|
+
handle.providerProtocol;
|
|
702
|
+
const metadataModel = mergedMetadata?.target && typeof mergedMetadata.target === 'object'
|
|
703
|
+
? mergedMetadata.target.clientModelId
|
|
704
|
+
: undefined;
|
|
705
|
+
const rawModel = this.extractProviderModel(providerPayload) ||
|
|
706
|
+
(typeof metadataModel === 'string' ? metadataModel : undefined);
|
|
707
|
+
const providerIdToken = target.providerKey || handle.providerId || runtimeKey;
|
|
708
|
+
if (!providerIdToken) {
|
|
709
|
+
throw Object.assign(new Error('Provider identifier missing for request'), {
|
|
710
|
+
code: 'ERR_PROVIDER_ID_MISSING',
|
|
711
|
+
requestId: providerRequestId
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
const enhancedRequestId = enhanceProviderRequestId(providerRequestId, {
|
|
768
715
|
entryEndpoint: input.entryEndpoint,
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
wantsStream: Boolean(input.metadata?.inboundStream ?? input.metadata?.stream),
|
|
772
|
-
originalRequest: originalRequestSnapshot,
|
|
773
|
-
processMode: pipelineResult.processMode,
|
|
774
|
-
response: normalized,
|
|
775
|
-
pipelineMetadata: mergedMetadata
|
|
716
|
+
providerId: providerIdToken,
|
|
717
|
+
model: rawModel
|
|
776
718
|
});
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
719
|
+
if (providerProtocol === 'openai-responses') {
|
|
720
|
+
try {
|
|
721
|
+
await rebindResponsesConversationRequestId(pipelineResult.requestId, enhancedRequestId);
|
|
722
|
+
}
|
|
723
|
+
catch {
|
|
724
|
+
/* ignore rebind failures */
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (enhancedRequestId !== input.requestId) {
|
|
728
|
+
input.requestId = enhancedRequestId;
|
|
729
|
+
}
|
|
730
|
+
mergedMetadata.clientRequestId = clientRequestId;
|
|
731
|
+
const providerModel = rawModel;
|
|
732
|
+
const providerLabel = this.buildProviderLabel(target.providerKey, providerModel);
|
|
733
|
+
this.logStage('provider.prepare', input.requestId, {
|
|
780
734
|
providerKey: target.providerKey,
|
|
781
|
-
|
|
735
|
+
runtimeKey,
|
|
736
|
+
protocol: providerProtocol,
|
|
782
737
|
providerType: handle.providerType,
|
|
783
738
|
providerFamily: handle.providerFamily,
|
|
784
739
|
model: providerModel,
|
|
785
740
|
providerLabel
|
|
786
741
|
});
|
|
787
|
-
|
|
742
|
+
attachProviderRuntimeMetadata(providerPayload, {
|
|
788
743
|
requestId: input.requestId,
|
|
789
|
-
providerKey: target.providerKey,
|
|
790
744
|
providerId: handle.providerId,
|
|
745
|
+
providerKey: target.providerKey,
|
|
791
746
|
providerType: handle.providerType,
|
|
747
|
+
providerFamily: handle.providerFamily,
|
|
792
748
|
providerProtocol,
|
|
793
|
-
routeName: pipelineResult.routingDecision?.routeName,
|
|
794
749
|
pipelineId: target.providerKey,
|
|
750
|
+
routeName: pipelineResult.routingDecision?.routeName,
|
|
795
751
|
runtimeKey,
|
|
796
|
-
target
|
|
797
|
-
|
|
798
|
-
runtimeMetadata.providerFamily = handle.providerFamily;
|
|
799
|
-
emitProviderError({
|
|
800
|
-
error,
|
|
801
|
-
stage: 'provider.send',
|
|
802
|
-
runtime: runtimeMetadata,
|
|
803
|
-
dependencies: this.getModuleDependencies()
|
|
752
|
+
target,
|
|
753
|
+
metadata: mergedMetadata
|
|
804
754
|
});
|
|
805
|
-
|
|
755
|
+
this.logStage('provider.send.start', input.requestId, {
|
|
756
|
+
providerKey: target.providerKey,
|
|
757
|
+
runtimeKey,
|
|
758
|
+
protocol: providerProtocol,
|
|
759
|
+
providerType: handle.providerType,
|
|
760
|
+
providerFamily: handle.providerFamily,
|
|
761
|
+
model: providerModel,
|
|
762
|
+
providerLabel
|
|
763
|
+
});
|
|
764
|
+
try {
|
|
765
|
+
const providerResponse = await handle.instance.processIncoming(providerPayload);
|
|
766
|
+
const responseStatus = this.extractResponseStatus(providerResponse);
|
|
767
|
+
this.logStage('provider.send.completed', input.requestId, {
|
|
768
|
+
providerKey: target.providerKey,
|
|
769
|
+
status: responseStatus,
|
|
770
|
+
providerType: handle.providerType,
|
|
771
|
+
providerFamily: handle.providerFamily,
|
|
772
|
+
model: providerModel,
|
|
773
|
+
providerLabel
|
|
774
|
+
});
|
|
775
|
+
const wantsStreamBase = Boolean(input.metadata?.inboundStream ?? input.metadata?.stream);
|
|
776
|
+
const normalized = this.normalizeProviderResponse(providerResponse);
|
|
777
|
+
const converted = await this.convertProviderResponseIfNeeded({
|
|
778
|
+
entryEndpoint: input.entryEndpoint,
|
|
779
|
+
providerType: handle.providerType,
|
|
780
|
+
requestId: input.requestId,
|
|
781
|
+
wantsStream: wantsStreamBase,
|
|
782
|
+
originalRequest: originalRequestSnapshot,
|
|
783
|
+
processMode: pipelineResult.processMode,
|
|
784
|
+
response: normalized,
|
|
785
|
+
pipelineMetadata: mergedMetadata
|
|
786
|
+
});
|
|
787
|
+
return converted;
|
|
788
|
+
}
|
|
789
|
+
catch (error) {
|
|
790
|
+
this.logStage('provider.send.error', input.requestId, {
|
|
791
|
+
providerKey: target.providerKey,
|
|
792
|
+
message: error instanceof Error ? error.message : String(error ?? 'Unknown error'),
|
|
793
|
+
providerType: handle.providerType,
|
|
794
|
+
providerFamily: handle.providerFamily,
|
|
795
|
+
model: providerModel,
|
|
796
|
+
providerLabel
|
|
797
|
+
});
|
|
798
|
+
const runtimeMetadata = {
|
|
799
|
+
requestId: input.requestId,
|
|
800
|
+
providerKey: target.providerKey,
|
|
801
|
+
providerId: handle.providerId,
|
|
802
|
+
providerType: handle.providerType,
|
|
803
|
+
providerProtocol,
|
|
804
|
+
routeName: pipelineResult.routingDecision?.routeName,
|
|
805
|
+
pipelineId: target.providerKey,
|
|
806
|
+
runtimeKey,
|
|
807
|
+
target
|
|
808
|
+
};
|
|
809
|
+
runtimeMetadata.providerFamily = handle.providerFamily;
|
|
810
|
+
emitProviderError({
|
|
811
|
+
error,
|
|
812
|
+
stage: 'provider.send',
|
|
813
|
+
runtime: runtimeMetadata,
|
|
814
|
+
dependencies: this.getModuleDependencies()
|
|
815
|
+
});
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
806
818
|
}
|
|
807
819
|
}
|
|
808
820
|
async runHubPipeline(input, metadata) {
|
|
@@ -940,12 +952,28 @@ export class RouteCodexHttpServer {
|
|
|
940
952
|
const metadataBag = asRecord(options.pipelineMetadata);
|
|
941
953
|
const aliasMap = extractAnthropicToolAliasMap(metadataBag);
|
|
942
954
|
const originalModelId = this.extractClientModelId(metadataBag, options.originalRequest);
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
955
|
+
// 以 HubPipeline metadata 为基础构建 AdapterContext,确保诸如
|
|
956
|
+
// capturedChatRequest / webSearch / routeHint 等字段在响应侧可见,
|
|
957
|
+
// 便于 llmswitch-core 内部实现 servertool/web_search 的第三跳。
|
|
958
|
+
const baseContext = {
|
|
959
|
+
...(metadataBag ?? {})
|
|
948
960
|
};
|
|
961
|
+
// 将 HubPipeline metadata.routeName 映射为 AdapterContext.routeId,
|
|
962
|
+
// 便于 llmswitch-core 在第三跳中使用 routeHint 复用首次路由决策。
|
|
963
|
+
if (typeof metadataBag?.routeName === 'string') {
|
|
964
|
+
baseContext.routeId = metadataBag.routeName;
|
|
965
|
+
}
|
|
966
|
+
baseContext.requestId = options.requestId;
|
|
967
|
+
baseContext.entryEndpoint = options.entryEndpoint || entry;
|
|
968
|
+
baseContext.providerProtocol = providerProtocol;
|
|
969
|
+
baseContext.originalModelId = originalModelId;
|
|
970
|
+
const adapterContext = baseContext;
|
|
971
|
+
// 将 serverToolFollowup 等标记从 pipelineMetadata 透传到 AdapterContext,
|
|
972
|
+
// 便于 convertProviderResponse 正确识别内部二跳请求并跳过 servertool。
|
|
973
|
+
if (metadataBag && Object.prototype.hasOwnProperty.call(metadataBag, 'serverToolFollowup')) {
|
|
974
|
+
adapterContext.serverToolFollowup = metadataBag
|
|
975
|
+
.serverToolFollowup;
|
|
976
|
+
}
|
|
949
977
|
const compatProfile = metadataBag &&
|
|
950
978
|
typeof metadataBag === 'object' &&
|
|
951
979
|
metadataBag.target &&
|
|
@@ -959,18 +987,85 @@ export class RouteCodexHttpServer {
|
|
|
959
987
|
if (aliasMap) {
|
|
960
988
|
adapterContext.anthropicToolNameMap = aliasMap;
|
|
961
989
|
}
|
|
990
|
+
if (metadataBag && typeof metadataBag === 'object') {
|
|
991
|
+
const webSearchConfig = metadataBag.webSearch;
|
|
992
|
+
if (webSearchConfig && typeof webSearchConfig === 'object') {
|
|
993
|
+
adapterContext.webSearch = webSearchConfig;
|
|
994
|
+
}
|
|
995
|
+
if (metadataBag.forceWebSearch === true) {
|
|
996
|
+
adapterContext.forceWebSearch = true;
|
|
997
|
+
}
|
|
998
|
+
if (metadataBag.forceVision === true) {
|
|
999
|
+
adapterContext.forceVision = true;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
962
1002
|
const [convertProviderResponse, createSnapshotRecorder] = await Promise.all([
|
|
963
1003
|
loadConvertProviderResponse(),
|
|
964
1004
|
loadSnapshotRecorderFactory()
|
|
965
1005
|
]);
|
|
966
|
-
const stageRecorder = createSnapshotRecorder(adapterContext, adapterContext.entryEndpoint
|
|
1006
|
+
const stageRecorder = createSnapshotRecorder(adapterContext, typeof adapterContext.entryEndpoint === 'string'
|
|
1007
|
+
? adapterContext.entryEndpoint
|
|
1008
|
+
: options.entryEndpoint || entry);
|
|
1009
|
+
const providerInvoker = async (invokeOptions) => {
|
|
1010
|
+
const runtimeKey = this.providerKeyToRuntimeKey.get(invokeOptions.providerKey) || invokeOptions.providerKey;
|
|
1011
|
+
const handle = this.providerHandles.get(runtimeKey);
|
|
1012
|
+
if (!handle) {
|
|
1013
|
+
throw new Error(`Provider runtime ${runtimeKey} not found`);
|
|
1014
|
+
}
|
|
1015
|
+
const providerResponse = await handle.instance.processIncoming(invokeOptions.payload);
|
|
1016
|
+
const normalized = this.normalizeProviderResponse(providerResponse);
|
|
1017
|
+
const bodyPayload = normalized.body && typeof normalized.body === 'object'
|
|
1018
|
+
? normalized.body
|
|
1019
|
+
: normalized;
|
|
1020
|
+
return { providerResponse: bodyPayload };
|
|
1021
|
+
};
|
|
1022
|
+
const reenterPipeline = async (reenterOpts) => {
|
|
1023
|
+
const nestedEntry = reenterOpts.entryEndpoint || options.entryEndpoint || entry;
|
|
1024
|
+
const nestedExtra = asRecord(reenterOpts.metadata) ?? {};
|
|
1025
|
+
const nestedEntryLower = nestedEntry.toLowerCase();
|
|
1026
|
+
// 基于首次 HubPipeline metadata + 调用方注入的 metadata 构建新的请求 metadata。
|
|
1027
|
+
// 不在 Host 层编码 servertool/web_search 等语义,由 llmswitch-core 负责。
|
|
1028
|
+
const nestedMetadata = {
|
|
1029
|
+
...(metadataBag ?? {}),
|
|
1030
|
+
...nestedExtra,
|
|
1031
|
+
entryEndpoint: nestedEntry,
|
|
1032
|
+
direction: 'request',
|
|
1033
|
+
stage: 'inbound'
|
|
1034
|
+
};
|
|
1035
|
+
// 针对 reenterPipeline 的入口端点,纠正 providerProtocol,避免沿用外层协议。
|
|
1036
|
+
if (nestedEntryLower.includes('/v1/chat/completions')) {
|
|
1037
|
+
nestedMetadata.providerProtocol = 'openai-chat';
|
|
1038
|
+
}
|
|
1039
|
+
else if (nestedEntryLower.includes('/v1/responses')) {
|
|
1040
|
+
nestedMetadata.providerProtocol = 'openai-responses';
|
|
1041
|
+
}
|
|
1042
|
+
else if (nestedEntryLower.includes('/v1/messages')) {
|
|
1043
|
+
nestedMetadata.providerProtocol = 'anthropic-messages';
|
|
1044
|
+
}
|
|
1045
|
+
const nestedInput = {
|
|
1046
|
+
entryEndpoint: nestedEntry,
|
|
1047
|
+
method: 'POST',
|
|
1048
|
+
requestId: reenterOpts.requestId,
|
|
1049
|
+
headers: {},
|
|
1050
|
+
query: {},
|
|
1051
|
+
body: reenterOpts.body,
|
|
1052
|
+
metadata: nestedMetadata
|
|
1053
|
+
};
|
|
1054
|
+
const nestedResult = await this.executePipeline(nestedInput);
|
|
1055
|
+
const nestedBody = nestedResult.body && typeof nestedResult.body === 'object'
|
|
1056
|
+
? nestedResult.body
|
|
1057
|
+
: undefined;
|
|
1058
|
+
return { body: nestedBody };
|
|
1059
|
+
};
|
|
967
1060
|
const converted = await convertProviderResponse({
|
|
968
1061
|
providerProtocol,
|
|
969
1062
|
providerResponse: body,
|
|
970
1063
|
context: adapterContext,
|
|
971
1064
|
entryEndpoint: options.entryEndpoint || entry,
|
|
972
1065
|
wantsStream: options.wantsStream,
|
|
973
|
-
|
|
1066
|
+
providerInvoker,
|
|
1067
|
+
stageRecorder,
|
|
1068
|
+
reenterPipeline
|
|
974
1069
|
});
|
|
975
1070
|
if (converted.__sse_responses) {
|
|
976
1071
|
return {
|
|
@@ -1020,6 +1115,279 @@ export class RouteCodexHttpServer {
|
|
|
1020
1115
|
return undefined;
|
|
1021
1116
|
}
|
|
1022
1117
|
}
|
|
1118
|
+
buildVisionFollowupPayload(options) {
|
|
1119
|
+
const { originalPayload, visionResponse } = options;
|
|
1120
|
+
if (!originalPayload || typeof originalPayload !== 'object') {
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
const clone = this.cloneRequestPayload(originalPayload) ?? { ...originalPayload };
|
|
1124
|
+
if (!clone) {
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
const visionText = this.extractVisionDescription(visionResponse?.body);
|
|
1128
|
+
if (!visionText) {
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
if (this.rewriteResponsesInput(clone, visionText)) {
|
|
1132
|
+
return clone;
|
|
1133
|
+
}
|
|
1134
|
+
if (this.rewriteChatMessages(clone, visionText)) {
|
|
1135
|
+
return clone;
|
|
1136
|
+
}
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
rewriteResponsesInput(payload, visionText) {
|
|
1140
|
+
const inputList = payload.input;
|
|
1141
|
+
if (!Array.isArray(inputList)) {
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
for (let i = inputList.length - 1; i >= 0; i -= 1) {
|
|
1145
|
+
const item = inputList[i];
|
|
1146
|
+
if (!item || typeof item !== 'object') {
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
const role = typeof item.role === 'string'
|
|
1150
|
+
? item.role
|
|
1151
|
+
: '';
|
|
1152
|
+
if (role !== 'user') {
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
const contentBlocks = Array.isArray(item.content)
|
|
1156
|
+
? [...item.content]
|
|
1157
|
+
: [];
|
|
1158
|
+
const originalText = this.extractTextFromContentBlocks(contentBlocks, ['input_text', 'text']);
|
|
1159
|
+
const textType = this.detectContentTextType(contentBlocks, 'input_text');
|
|
1160
|
+
const composed = this.composeVisionUserText(visionText, originalText);
|
|
1161
|
+
item.content = [
|
|
1162
|
+
{
|
|
1163
|
+
type: textType,
|
|
1164
|
+
text: composed
|
|
1165
|
+
}
|
|
1166
|
+
];
|
|
1167
|
+
inputList[i] = item;
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
rewriteChatMessages(payload, visionText) {
|
|
1173
|
+
const messages = payload.messages;
|
|
1174
|
+
if (!Array.isArray(messages)) {
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
1178
|
+
const message = messages[i];
|
|
1179
|
+
if (!message || typeof message !== 'object') {
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
const role = typeof message.role === 'string'
|
|
1183
|
+
? message.role
|
|
1184
|
+
: '';
|
|
1185
|
+
if (role !== 'user') {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
const contentBlocks = Array.isArray(message.content)
|
|
1189
|
+
? [...message.content]
|
|
1190
|
+
: [];
|
|
1191
|
+
const originalText = this.extractTextFromContentBlocks(contentBlocks, ['text']);
|
|
1192
|
+
const textType = this.detectContentTextType(contentBlocks, 'text');
|
|
1193
|
+
const composed = this.composeVisionUserText(visionText, originalText);
|
|
1194
|
+
message.content = [
|
|
1195
|
+
{
|
|
1196
|
+
type: textType,
|
|
1197
|
+
text: composed
|
|
1198
|
+
}
|
|
1199
|
+
];
|
|
1200
|
+
messages[i] = message;
|
|
1201
|
+
return true;
|
|
1202
|
+
}
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
extractTextFromContentBlocks(content, allowedTypes) {
|
|
1206
|
+
if (typeof content === 'string') {
|
|
1207
|
+
return content;
|
|
1208
|
+
}
|
|
1209
|
+
if (!Array.isArray(content)) {
|
|
1210
|
+
return '';
|
|
1211
|
+
}
|
|
1212
|
+
const collected = [];
|
|
1213
|
+
for (const block of content) {
|
|
1214
|
+
if (!block || typeof block !== 'object') {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
const typeValue = typeof block.type === 'string'
|
|
1218
|
+
? block.type
|
|
1219
|
+
: '';
|
|
1220
|
+
if (allowedTypes.length && !allowedTypes.includes(typeValue)) {
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
const textValue = block.text;
|
|
1224
|
+
if (typeof textValue === 'string' && textValue.trim()) {
|
|
1225
|
+
collected.push(textValue.trim());
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return collected.join('\n');
|
|
1229
|
+
}
|
|
1230
|
+
detectContentTextType(content, fallback) {
|
|
1231
|
+
if (Array.isArray(content)) {
|
|
1232
|
+
for (const block of content) {
|
|
1233
|
+
if (!block || typeof block !== 'object') {
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
const typeValue = block.type;
|
|
1237
|
+
if (typeof typeValue === 'string' && (typeValue === 'text' || typeValue === 'input_text')) {
|
|
1238
|
+
return typeValue;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return fallback;
|
|
1243
|
+
}
|
|
1244
|
+
composeVisionUserText(visionText, originalText) {
|
|
1245
|
+
const sections = [];
|
|
1246
|
+
const cleanedVision = (visionText || '').trim();
|
|
1247
|
+
if (cleanedVision) {
|
|
1248
|
+
sections.push(`【图片分析】\n${cleanedVision}`);
|
|
1249
|
+
}
|
|
1250
|
+
const cleanedOriginal = (originalText || '').trim();
|
|
1251
|
+
if (cleanedOriginal) {
|
|
1252
|
+
sections.push(`【用户原始请求】\n${cleanedOriginal}`);
|
|
1253
|
+
}
|
|
1254
|
+
return sections.join('\n\n');
|
|
1255
|
+
}
|
|
1256
|
+
extractVisionDescription(body) {
|
|
1257
|
+
if (!body) {
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
if (typeof body === 'string') {
|
|
1261
|
+
const trimmed = body.trim();
|
|
1262
|
+
return trimmed.length ? trimmed : null;
|
|
1263
|
+
}
|
|
1264
|
+
if (typeof body !== 'object') {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
const record = body;
|
|
1268
|
+
const direct = this.extractTextCandidate(record);
|
|
1269
|
+
if (direct) {
|
|
1270
|
+
return direct;
|
|
1271
|
+
}
|
|
1272
|
+
if (record.response && typeof record.response === 'object') {
|
|
1273
|
+
const responseNode = record.response;
|
|
1274
|
+
const nested = this.extractTextCandidate(responseNode);
|
|
1275
|
+
if (nested) {
|
|
1276
|
+
return nested;
|
|
1277
|
+
}
|
|
1278
|
+
const output = responseNode.output;
|
|
1279
|
+
if (Array.isArray(output)) {
|
|
1280
|
+
for (const entry of output) {
|
|
1281
|
+
if (entry && typeof entry === 'object') {
|
|
1282
|
+
const nestedText = this.extractTextCandidate(entry);
|
|
1283
|
+
if (nestedText) {
|
|
1284
|
+
return nestedText;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
if (Array.isArray(record.output)) {
|
|
1291
|
+
for (const entry of record.output) {
|
|
1292
|
+
if (entry && typeof entry === 'object') {
|
|
1293
|
+
const nested = this.extractTextCandidate(entry);
|
|
1294
|
+
if (nested) {
|
|
1295
|
+
return nested;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
const choices = record.choices;
|
|
1301
|
+
if (Array.isArray(choices)) {
|
|
1302
|
+
for (const choice of choices) {
|
|
1303
|
+
if (!choice || typeof choice !== 'object') {
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
const message = choice.message;
|
|
1307
|
+
if (message && typeof message === 'object') {
|
|
1308
|
+
const msg = message;
|
|
1309
|
+
const content = msg.content;
|
|
1310
|
+
if (typeof content === 'string' && content.trim()) {
|
|
1311
|
+
return content.trim();
|
|
1312
|
+
}
|
|
1313
|
+
if (Array.isArray(content)) {
|
|
1314
|
+
for (const part of content) {
|
|
1315
|
+
if (part && typeof part === 'object' && typeof part.text === 'string') {
|
|
1316
|
+
const textValue = part.text.trim();
|
|
1317
|
+
if (textValue) {
|
|
1318
|
+
return textValue;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
extractTextCandidate(record) {
|
|
1329
|
+
const candidates = [
|
|
1330
|
+
{ key: 'output_text', allowJson: true },
|
|
1331
|
+
{ key: 'text' },
|
|
1332
|
+
{ key: 'content' }
|
|
1333
|
+
];
|
|
1334
|
+
for (const candidate of candidates) {
|
|
1335
|
+
if (!(candidate.key in record)) {
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
const text = this.normalizeTextCandidateValue(record[candidate.key], candidate.allowJson === true);
|
|
1339
|
+
if (text) {
|
|
1340
|
+
return text;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
normalizeTextCandidateValue(value, allowJsonStringify = false) {
|
|
1346
|
+
if (!value) {
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
if (typeof value === 'string') {
|
|
1350
|
+
const trimmed = value.trim();
|
|
1351
|
+
return trimmed.length ? trimmed : null;
|
|
1352
|
+
}
|
|
1353
|
+
if (Array.isArray(value)) {
|
|
1354
|
+
const collected = [];
|
|
1355
|
+
for (const entry of value) {
|
|
1356
|
+
const nested = this.normalizeTextCandidateValue(entry, allowJsonStringify);
|
|
1357
|
+
if (nested) {
|
|
1358
|
+
collected.push(nested);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return collected.length ? collected.join('\n') : null;
|
|
1362
|
+
}
|
|
1363
|
+
if (typeof value === 'object') {
|
|
1364
|
+
const bag = value;
|
|
1365
|
+
const textField = bag.text;
|
|
1366
|
+
if (typeof textField === 'string' && textField.trim()) {
|
|
1367
|
+
return textField.trim();
|
|
1368
|
+
}
|
|
1369
|
+
const summaryField = bag.summary;
|
|
1370
|
+
if (typeof summaryField === 'string' && summaryField.trim()) {
|
|
1371
|
+
return summaryField.trim();
|
|
1372
|
+
}
|
|
1373
|
+
if ('content' in bag) {
|
|
1374
|
+
const nested = this.normalizeTextCandidateValue(bag.content, allowJsonStringify);
|
|
1375
|
+
if (nested) {
|
|
1376
|
+
return nested;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (allowJsonStringify) {
|
|
1380
|
+
try {
|
|
1381
|
+
const serialized = JSON.stringify(value, null, 2);
|
|
1382
|
+
return serialized.trim() || null;
|
|
1383
|
+
}
|
|
1384
|
+
catch {
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return null;
|
|
1390
|
+
}
|
|
1023
1391
|
async initializeRouteErrorHub() {
|
|
1024
1392
|
try {
|
|
1025
1393
|
this.routeErrorHub = initializeRouteErrorHub({ errorHandlingCenter: this.errorHandling });
|