@jsonstudio/llms 0.6.3541 → 0.6.3685
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/conversion/compat/actions/antigravity-thought-signature-cache.js +23 -114
- package/dist/conversion/compat/actions/auto-thinking.js +3 -2
- package/dist/conversion/compat/actions/deepseek-web-response.js +9 -50
- package/dist/conversion/compat/actions/field-mapping.js +2 -153
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +1 -1
- package/dist/conversion/compat/actions/glm-history-image-trim.js +3 -37
- package/dist/conversion/compat/actions/glm-image-content.js +3 -32
- package/dist/conversion/compat/actions/glm-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/glm-native-compat.js +34 -0
- package/dist/conversion/compat/actions/glm-vision-prompt.js +3 -76
- package/dist/conversion/compat/actions/glm-web-search.js +10 -43
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +4 -53
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +5 -141
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +7 -28
- package/dist/conversion/compat/actions/iflow-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/iflow-native-compat.js +36 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +4 -119
- package/dist/conversion/compat/actions/iflow-web-search.js +14 -55
- package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.js +3 -104
- package/dist/conversion/hub/node-support.js +1 -1
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +9 -1
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +5 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +34 -14
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +14 -14
- package/dist/conversion/hub/pipeline/hub-pipeline.js +838 -524
- package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +6 -0
- package/dist/conversion/hub/pipeline/hub-stage-timing.js +178 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +6 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +46 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.d.ts +3 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.js +2 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +2 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +1 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.d.ts +3 -2
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.js +18 -5
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.d.ts +1 -2
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.js +0 -16
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +30 -12
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +1 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +5 -2
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +9 -5
- package/dist/conversion/hub/process/chat-process-continue-execution.js +2 -4
- package/dist/conversion/hub/process/chat-process-governance-orchestration.js +3 -1
- package/dist/conversion/hub/process/chat-process-media.d.ts +1 -0
- package/dist/conversion/hub/process/chat-process-media.js +36 -0
- package/dist/conversion/hub/process/chat-process-session-usage.d.ts +25 -0
- package/dist/conversion/hub/process/chat-process-session-usage.js +246 -0
- package/dist/conversion/hub/response/provider-response.js +13 -0
- package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +0 -4
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +0 -12
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -9
- package/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
- package/dist/conversion/responses/responses-openai-bridge.js +51 -24
- package/dist/conversion/shared/anthropic-message-utils.js +14 -1
- package/dist/conversion/shared/reasoning-normalizer.js +61 -0
- package/dist/conversion/shared/tool-governor.js +2 -4
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/quota/quota-state.js +1 -6
- package/dist/router/virtual-router/bootstrap/profile-builder.js +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +6 -0
- package/dist/router/virtual-router/bootstrap.js +1 -6
- package/dist/router/virtual-router/engine/routing-state/store.js +21 -2
- package/dist/router/virtual-router/engine-legacy.js +43 -0
- package/dist/router/virtual-router/engine-logging.d.ts +3 -0
- package/dist/router/virtual-router/engine-logging.js +29 -3
- package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.js +1 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +72 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +1 -1
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +1 -1
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.d.ts +0 -1
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.js +0 -29
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +6 -2
- package/dist/router/virtual-router/engine.js +28 -13
- package/dist/router/virtual-router/provider-registry.js +1 -0
- package/dist/router/virtual-router/routing-instructions/state.js +44 -2
- package/dist/router/virtual-router/routing-instructions/types.d.ts +6 -0
- package/dist/router/virtual-router/token-estimator.js +21 -0
- package/dist/router/virtual-router/types.d.ts +7 -0
- package/dist/servertool/engine.js +3 -34
- package/dist/servertool/handlers/followup-request-builder.js +0 -6
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +3 -274
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +0 -3
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +0 -29
- package/dist/servertool/handlers/stop-message-auto.js +11 -9
- package/dist/servertool/handlers/vision.js +4 -1
- package/dist/servertool/server-side-tools.d.ts +0 -1
- package/dist/servertool/server-side-tools.js +67 -5
- package/dist/tools/apply-patch/execution-capturer.d.ts +1 -1
- package/dist/tools/apply-patch/execution-capturer.js +1 -2
- package/dist/tools/apply-patch/regression-capturer.js +2 -1
- package/dist/tools/tool-registry.js +1 -2
- package/package.json +1 -1
|
@@ -1,54 +1,164 @@
|
|
|
1
|
-
import { Readable } from
|
|
2
|
-
import { isJsonObject, jsonClone } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { isJsonObject, jsonClone } from "../types/json.js";
|
|
3
|
+
import { convertMessagesToBridgeInput } from "../../bridge-message-utils.js";
|
|
4
|
+
import { VirtualRouterEngine } from "../../../router/virtual-router/engine.js";
|
|
5
|
+
import { providerErrorCenter } from "../../../router/virtual-router/error-center.js";
|
|
6
|
+
import { providerSuccessCenter } from "../../../router/virtual-router/success-center.js";
|
|
7
|
+
import { defaultSseCodecRegistry, } from "../../../sse/index.js";
|
|
8
|
+
import { ResponsesFormatAdapter } from "../format-adapters/responses-format-adapter.js";
|
|
9
|
+
import { ResponsesSemanticMapper } from "../semantic-mappers/responses-mapper.js";
|
|
10
|
+
import { AnthropicFormatAdapter } from "../format-adapters/anthropic-format-adapter.js";
|
|
11
|
+
import { AnthropicSemanticMapper } from "../semantic-mappers/anthropic-mapper.js";
|
|
12
|
+
import { GeminiFormatAdapter } from "../format-adapters/gemini-format-adapter.js";
|
|
13
|
+
import { GeminiSemanticMapper } from "../semantic-mappers/gemini-mapper.js";
|
|
14
|
+
import { ChatFormatAdapter } from "../format-adapters/chat-format-adapter.js";
|
|
15
|
+
import { ChatSemanticMapper } from "../semantic-mappers/chat-mapper.js";
|
|
16
|
+
import { createSnapshotRecorder } from "../snapshot-recorder.js";
|
|
17
|
+
import { shouldRecordSnapshots } from "../../snapshot-utils.js";
|
|
18
|
+
import { measureHubStage } from "./hub-stage-timing.js";
|
|
19
|
+
import { runReqInboundStage1FormatParse } from "./stages/req_inbound/req_inbound_stage1_format_parse/index.js";
|
|
20
|
+
import { runReqInboundStage2SemanticMap } from "./stages/req_inbound/req_inbound_stage2_semantic_map/index.js";
|
|
21
|
+
import { runChatContextCapture, captureResponsesContextSnapshot, } from "./stages/req_inbound/req_inbound_stage3_context_capture/index.js";
|
|
22
|
+
import { normalizeReqInboundToolCallIdStyleWithNative } from "../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js";
|
|
23
|
+
import { createResponsesContextCapture, createNoopContextCapture, } from "./stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js";
|
|
24
|
+
import { runReqProcessStage1ToolGovernance } from "./stages/req_process/req_process_stage1_tool_governance/index.js";
|
|
25
|
+
import { runReqProcessStage2RouteSelect } from "./stages/req_process/req_process_stage2_route_select/index.js";
|
|
26
|
+
import { runReqOutboundStage1SemanticMap } from "./stages/req_outbound/req_outbound_stage1_semantic_map/index.js";
|
|
27
|
+
import { runReqOutboundStage2FormatBuild } from "./stages/req_outbound/req_outbound_stage2_format_build/index.js";
|
|
28
|
+
import { runReqOutboundStage3Compat } from "./stages/req_outbound/req_outbound_stage3_compat/index.js";
|
|
29
|
+
import { extractSessionIdentifiersFromMetadata } from "./session-identifiers.js";
|
|
30
|
+
import { computeRequestTokens } from "../../../router/virtual-router/token-estimator.js";
|
|
31
|
+
import { annotatePassthroughGovernanceSkipWithNative, attachPassthroughProviderInputAuditWithNative, buildPassthroughAuditWithNative, applyOutboundStreamPreferenceWithNative, normalizeHubEndpointWithNative, extractAdapterContextMetadataFieldsWithNative, resolveApplyPatchToolModeFromToolsWithNative, resolveHubClientProtocolWithNative, resolveHubPolicyOverrideFromMetadataWithNative, resolveHubProviderProtocolWithNative, resolveOutboundStreamIntentWithNative, resolveHubShadowCompareConfigWithNative, resolveActiveProcessModeWithNative, findMappableSemanticsKeysWithNative, resolveHubSseProtocolFromMetadataWithNative, resolveStopMessageRouterMetadataWithNative, runHubPipelineOrchestrationWithNative, } from "../../../router/virtual-router/engine-selection/native-hub-pipeline-orchestration-semantics.js";
|
|
32
|
+
import { normalizeAliasMapWithNative, resolveAliasMapFromRespSemanticsWithNative, } from "../../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js";
|
|
33
|
+
import { isCompactionRequest } from "../../compaction-detect.js";
|
|
34
|
+
import { applyHubProviderOutboundPolicy, recordHubPolicyObservation, setHubPolicyRuntimePolicy, } from "../policy/policy-engine.js";
|
|
35
|
+
import { applyProviderOutboundToolSurface, } from "../tool-surface/tool-surface-engine.js";
|
|
36
|
+
import { cloneRuntimeMetadata, ensureRuntimeMetadata, readRuntimeMetadata, } from "../../runtime-metadata.js";
|
|
37
|
+
import { containsImageAttachment, stripHistoricalImageAttachments, stripHistoricalVisualToolOutputs, } from "../process/chat-process-media.js";
|
|
38
|
+
import { estimateChatProcessSessionInputTokensDetailed, saveChatProcessSessionInputEstimate, } from "../process/chat-process-session-usage.js";
|
|
35
39
|
function isTruthyEnv(value) {
|
|
36
|
-
const v = typeof value ===
|
|
37
|
-
return v ===
|
|
40
|
+
const v = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
41
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
38
42
|
}
|
|
39
43
|
function resolveApplyPatchToolModeFromEnv() {
|
|
40
|
-
const rawMode = String(process.env.RCC_APPLY_PATCH_TOOL_MODE ||
|
|
44
|
+
const rawMode = String(process.env.RCC_APPLY_PATCH_TOOL_MODE ||
|
|
45
|
+
process.env.ROUTECODEX_APPLY_PATCH_TOOL_MODE ||
|
|
46
|
+
"")
|
|
41
47
|
.trim()
|
|
42
48
|
.toLowerCase();
|
|
43
|
-
if (rawMode ===
|
|
44
|
-
return
|
|
45
|
-
if (rawMode ===
|
|
46
|
-
return
|
|
47
|
-
const freeformFlag = process.env.RCC_APPLY_PATCH_FREEFORM ||
|
|
49
|
+
if (rawMode === "freeform")
|
|
50
|
+
return "freeform";
|
|
51
|
+
if (rawMode === "schema" || rawMode === "json_schema")
|
|
52
|
+
return "schema";
|
|
53
|
+
const freeformFlag = process.env.RCC_APPLY_PATCH_FREEFORM ||
|
|
54
|
+
process.env.ROUTECODEX_APPLY_PATCH_FREEFORM;
|
|
48
55
|
if (isTruthyEnv(freeformFlag))
|
|
49
|
-
return
|
|
56
|
+
return "freeform";
|
|
50
57
|
return undefined;
|
|
51
58
|
}
|
|
59
|
+
function applyChatProcessEntryMediaCleanup(request) {
|
|
60
|
+
return {
|
|
61
|
+
...request,
|
|
62
|
+
messages: stripHistoricalVisualToolOutputs(stripHistoricalImageAttachments(request.messages)),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function readResponsesResumeFromMetadata(metadata) {
|
|
66
|
+
if (!metadata || typeof metadata !== "object") {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const resume = metadata.responsesResume;
|
|
70
|
+
return resume && isJsonObject(resume)
|
|
71
|
+
? resume
|
|
72
|
+
: undefined;
|
|
73
|
+
}
|
|
74
|
+
function readResponsesResumeFromRequestSemantics(request) {
|
|
75
|
+
try {
|
|
76
|
+
const semantics = request?.semantics;
|
|
77
|
+
const responses = semantics &&
|
|
78
|
+
typeof semantics === "object" &&
|
|
79
|
+
!Array.isArray(semantics) &&
|
|
80
|
+
semantics.responses &&
|
|
81
|
+
typeof semantics.responses === "object" &&
|
|
82
|
+
!Array.isArray(semantics.responses)
|
|
83
|
+
? semantics.responses
|
|
84
|
+
: undefined;
|
|
85
|
+
const resume = responses &&
|
|
86
|
+
responses.resume &&
|
|
87
|
+
typeof responses.resume === "object" &&
|
|
88
|
+
!Array.isArray(responses.resume)
|
|
89
|
+
? responses.resume
|
|
90
|
+
: undefined;
|
|
91
|
+
return resume;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function liftResponsesResumeIntoSemantics(request, metadata) {
|
|
98
|
+
const resumeMeta = readResponsesResumeFromMetadata(metadata);
|
|
99
|
+
if (!resumeMeta) {
|
|
100
|
+
return request;
|
|
101
|
+
}
|
|
102
|
+
const next = {
|
|
103
|
+
...request,
|
|
104
|
+
semantics: {
|
|
105
|
+
...(request.semantics ??
|
|
106
|
+
{}),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const semantics = next.semantics;
|
|
110
|
+
if (!semantics.responses ||
|
|
111
|
+
typeof semantics.responses !== "object" ||
|
|
112
|
+
Array.isArray(semantics.responses)) {
|
|
113
|
+
semantics.responses = {};
|
|
114
|
+
}
|
|
115
|
+
const responsesNode = semantics.responses;
|
|
116
|
+
if (responsesNode.resume === undefined) {
|
|
117
|
+
responsesNode.resume = jsonClone(resumeMeta);
|
|
118
|
+
}
|
|
119
|
+
delete metadata.responsesResume;
|
|
120
|
+
return next;
|
|
121
|
+
}
|
|
122
|
+
function syncResponsesContextFromCanonicalMessages(request) {
|
|
123
|
+
const semantics = request?.semantics;
|
|
124
|
+
const responsesNode = semantics &&
|
|
125
|
+
typeof semantics === "object" &&
|
|
126
|
+
!Array.isArray(semantics) &&
|
|
127
|
+
semantics.responses &&
|
|
128
|
+
typeof semantics.responses === "object" &&
|
|
129
|
+
!Array.isArray(semantics.responses)
|
|
130
|
+
? semantics.responses
|
|
131
|
+
: undefined;
|
|
132
|
+
const contextNode = responsesNode &&
|
|
133
|
+
responsesNode.context &&
|
|
134
|
+
typeof responsesNode.context === "object" &&
|
|
135
|
+
!Array.isArray(responsesNode.context)
|
|
136
|
+
? responsesNode.context
|
|
137
|
+
: undefined;
|
|
138
|
+
if (!contextNode) {
|
|
139
|
+
return request;
|
|
140
|
+
}
|
|
141
|
+
const bridge = convertMessagesToBridgeInput({
|
|
142
|
+
messages: request.messages ?? [],
|
|
143
|
+
tools: Array.isArray(request.tools)
|
|
144
|
+
? request.tools
|
|
145
|
+
: undefined,
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
...request,
|
|
149
|
+
semantics: {
|
|
150
|
+
...semantics,
|
|
151
|
+
responses: {
|
|
152
|
+
...responsesNode,
|
|
153
|
+
context: {
|
|
154
|
+
...contextNode,
|
|
155
|
+
input: jsonClone(bridge.input),
|
|
156
|
+
originalSystemMessages: jsonClone(bridge.originalSystemMessages),
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
52
162
|
function resolveApplyPatchToolModeFromTools(toolsRaw) {
|
|
53
163
|
return resolveApplyPatchToolModeFromToolsWithNative(toolsRaw);
|
|
54
164
|
}
|
|
@@ -59,7 +169,9 @@ function extractHubPolicyOverride(metadata) {
|
|
|
59
169
|
}
|
|
60
170
|
return {
|
|
61
171
|
mode: parsed.mode,
|
|
62
|
-
...(parsed.sampleRate !== undefined
|
|
172
|
+
...(parsed.sampleRate !== undefined
|
|
173
|
+
? { sampleRate: parsed.sampleRate }
|
|
174
|
+
: {}),
|
|
63
175
|
};
|
|
64
176
|
}
|
|
65
177
|
function propagateAdapterContextMetadataFields(adapterContext, metadata, keys) {
|
|
@@ -70,75 +182,92 @@ function resolveStopMessageRouterMetadata(metadata) {
|
|
|
70
182
|
return resolveStopMessageRouterMetadataWithNative(metadata);
|
|
71
183
|
}
|
|
72
184
|
function isSearchRouteId(routeId) {
|
|
73
|
-
const normalized = typeof routeId ===
|
|
74
|
-
return normalized.startsWith(
|
|
185
|
+
const normalized = typeof routeId === "string" ? routeId.trim().toLowerCase() : "";
|
|
186
|
+
return normalized.startsWith("web_search") || normalized.startsWith("search");
|
|
75
187
|
}
|
|
76
188
|
function isCanonicalWebSearchToolDefinition(tool) {
|
|
77
|
-
if (!tool || typeof tool !==
|
|
189
|
+
if (!tool || typeof tool !== "object" || Array.isArray(tool)) {
|
|
78
190
|
return false;
|
|
79
191
|
}
|
|
80
192
|
const row = tool;
|
|
81
|
-
const rawType = typeof row.type ===
|
|
82
|
-
if (rawType ===
|
|
193
|
+
const rawType = typeof row.type === "string" ? row.type.trim().toLowerCase() : "";
|
|
194
|
+
if (rawType === "web_search_20250305" || rawType === "web_search") {
|
|
83
195
|
return true;
|
|
84
196
|
}
|
|
85
|
-
const fnNode = row.function &&
|
|
197
|
+
const fnNode = row.function &&
|
|
198
|
+
typeof row.function === "object" &&
|
|
199
|
+
!Array.isArray(row.function)
|
|
86
200
|
? row.function
|
|
87
201
|
: undefined;
|
|
88
|
-
const name = typeof fnNode?.name ===
|
|
202
|
+
const name = typeof fnNode?.name === "string"
|
|
89
203
|
? fnNode.name.trim().toLowerCase()
|
|
90
|
-
: typeof row.name ===
|
|
204
|
+
: typeof row.name === "string"
|
|
91
205
|
? row.name.trim().toLowerCase()
|
|
92
|
-
:
|
|
93
|
-
return name ===
|
|
206
|
+
: "";
|
|
207
|
+
return name === "web_search" || name === "websearch" || name === "web-search";
|
|
94
208
|
}
|
|
95
209
|
function maybeApplyDirectBuiltinWebSearchTool(providerPayload, adapterContext, providerProtocol) {
|
|
96
|
-
if (providerProtocol !==
|
|
210
|
+
if (providerProtocol !== "anthropic-messages") {
|
|
97
211
|
return providerPayload;
|
|
98
212
|
}
|
|
99
213
|
if (!isSearchRouteId(adapterContext.routeId)) {
|
|
100
214
|
return providerPayload;
|
|
101
215
|
}
|
|
102
|
-
const modelId = typeof providerPayload.model ===
|
|
216
|
+
const modelId = typeof providerPayload.model === "string"
|
|
217
|
+
? providerPayload.model.trim()
|
|
218
|
+
: "";
|
|
103
219
|
if (!modelId) {
|
|
104
220
|
return providerPayload;
|
|
105
221
|
}
|
|
106
222
|
const rt = readRuntimeMetadata(adapterContext);
|
|
107
|
-
const webSearch = rt &&
|
|
223
|
+
const webSearch = rt &&
|
|
224
|
+
typeof rt.webSearch === "object" &&
|
|
225
|
+
rt.webSearch &&
|
|
226
|
+
!Array.isArray(rt.webSearch)
|
|
108
227
|
? rt.webSearch
|
|
109
228
|
: undefined;
|
|
110
|
-
const enginesRaw = Array.isArray(webSearch?.engines)
|
|
229
|
+
const enginesRaw = Array.isArray(webSearch?.engines)
|
|
230
|
+
? webSearch?.engines
|
|
231
|
+
: [];
|
|
111
232
|
const matchedEngine = enginesRaw.find((entry) => {
|
|
112
|
-
if (!entry || typeof entry !==
|
|
233
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
113
234
|
return false;
|
|
114
235
|
}
|
|
115
236
|
const row = entry;
|
|
116
|
-
const executionMode = typeof row.executionMode ===
|
|
117
|
-
|
|
237
|
+
const executionMode = typeof row.executionMode === "string"
|
|
238
|
+
? row.executionMode.trim().toLowerCase()
|
|
239
|
+
: "";
|
|
240
|
+
if (executionMode !== "direct") {
|
|
118
241
|
return false;
|
|
119
242
|
}
|
|
120
|
-
const directActivation = typeof row.directActivation ===
|
|
121
|
-
|
|
243
|
+
const directActivation = typeof row.directActivation === "string"
|
|
244
|
+
? row.directActivation.trim().toLowerCase()
|
|
245
|
+
: "route";
|
|
246
|
+
if (directActivation !== "builtin") {
|
|
122
247
|
return false;
|
|
123
248
|
}
|
|
124
|
-
const configuredModelId = typeof row.modelId ===
|
|
249
|
+
const configuredModelId = typeof row.modelId === "string" ? row.modelId.trim() : "";
|
|
125
250
|
if (configuredModelId && configuredModelId === modelId) {
|
|
126
251
|
return true;
|
|
127
252
|
}
|
|
128
|
-
const providerKey = typeof row.providerKey ===
|
|
253
|
+
const providerKey = typeof row.providerKey === "string" ? row.providerKey.trim() : "";
|
|
129
254
|
return providerKey.endsWith(`.${modelId}`);
|
|
130
255
|
});
|
|
131
256
|
if (!matchedEngine) {
|
|
132
257
|
return providerPayload;
|
|
133
258
|
}
|
|
134
|
-
const rawMaxUses = typeof matchedEngine.maxUses ===
|
|
259
|
+
const rawMaxUses = typeof matchedEngine.maxUses === "number"
|
|
260
|
+
? matchedEngine.maxUses
|
|
261
|
+
: Number(matchedEngine.maxUses);
|
|
135
262
|
const maxUses = Number.isFinite(rawMaxUses) && rawMaxUses > 0 ? Math.floor(rawMaxUses) : 2;
|
|
136
263
|
const builtinTool = {
|
|
137
|
-
type:
|
|
138
|
-
name:
|
|
139
|
-
max_uses: maxUses
|
|
264
|
+
type: "web_search_20250305",
|
|
265
|
+
name: "web_search",
|
|
266
|
+
max_uses: maxUses,
|
|
140
267
|
};
|
|
141
|
-
const tools = Array.isArray(providerPayload.tools)
|
|
268
|
+
const tools = Array.isArray(providerPayload.tools)
|
|
269
|
+
? providerPayload.tools
|
|
270
|
+
: [];
|
|
142
271
|
let replaced = false;
|
|
143
272
|
const nextTools = [];
|
|
144
273
|
for (const tool of tools) {
|
|
@@ -195,7 +324,7 @@ export class HubPipeline {
|
|
|
195
324
|
this.routerEngine = new VirtualRouterEngine({
|
|
196
325
|
healthStore: config.healthStore,
|
|
197
326
|
routingStateStore: config.routingStateStore,
|
|
198
|
-
quotaView: config.quotaView
|
|
327
|
+
quotaView: config.quotaView,
|
|
199
328
|
});
|
|
200
329
|
this.routerEngine.initialize(config.virtualRouter);
|
|
201
330
|
setHubPolicyRuntimePolicy(config.policy);
|
|
@@ -227,23 +356,24 @@ export class HubPipeline {
|
|
|
227
356
|
}
|
|
228
357
|
}
|
|
229
358
|
updateRuntimeDeps(deps) {
|
|
230
|
-
if (!deps || typeof deps !==
|
|
359
|
+
if (!deps || typeof deps !== "object") {
|
|
231
360
|
return;
|
|
232
361
|
}
|
|
233
|
-
if (
|
|
362
|
+
if ("healthStore" in deps) {
|
|
234
363
|
this.config.healthStore = deps.healthStore ?? undefined;
|
|
235
364
|
}
|
|
236
|
-
if (
|
|
237
|
-
this.config.routingStateStore = (deps.routingStateStore ??
|
|
365
|
+
if ("routingStateStore" in deps) {
|
|
366
|
+
this.config.routingStateStore = (deps.routingStateStore ??
|
|
367
|
+
undefined);
|
|
238
368
|
}
|
|
239
|
-
if (
|
|
369
|
+
if ("quotaView" in deps) {
|
|
240
370
|
this.config.quotaView = deps.quotaView ?? undefined;
|
|
241
371
|
}
|
|
242
372
|
try {
|
|
243
373
|
this.routerEngine.updateDeps({
|
|
244
374
|
healthStore: this.config.healthStore ?? null,
|
|
245
375
|
routingStateStore: (this.config.routingStateStore ?? null),
|
|
246
|
-
quotaView: this.config.quotaView ?? null
|
|
376
|
+
quotaView: this.config.quotaView ?? null,
|
|
247
377
|
});
|
|
248
378
|
}
|
|
249
379
|
catch {
|
|
@@ -251,8 +381,8 @@ export class HubPipeline {
|
|
|
251
381
|
}
|
|
252
382
|
}
|
|
253
383
|
updateVirtualRouterConfig(nextConfig) {
|
|
254
|
-
if (!nextConfig || typeof nextConfig !==
|
|
255
|
-
throw new Error(
|
|
384
|
+
if (!nextConfig || typeof nextConfig !== "object") {
|
|
385
|
+
throw new Error("HubPipeline updateVirtualRouterConfig requires VirtualRouterConfig payload");
|
|
256
386
|
}
|
|
257
387
|
this.config.virtualRouter = nextConfig;
|
|
258
388
|
this.routerEngine.initialize(nextConfig);
|
|
@@ -280,20 +410,6 @@ export class HubPipeline {
|
|
|
280
410
|
async executeRequestStagePipeline(normalized, hooks) {
|
|
281
411
|
const semanticMapper = hooks.createSemanticMapper();
|
|
282
412
|
const rawRequest = this.asJsonObject(normalized.payload);
|
|
283
|
-
// Detect applyPatchToolMode (runtime/tooling hint). Client tool schemas are captured as chat semantics
|
|
284
|
-
// in req_inbound_stage2_semantic_map; they must not be stored in metadata.
|
|
285
|
-
try {
|
|
286
|
-
const toolsRaw = Array.isArray(rawRequest?.tools) ? rawRequest.tools : null;
|
|
287
|
-
const applyPatchToolMode = resolveApplyPatchToolModeFromEnv() ?? resolveApplyPatchToolModeFromTools(toolsRaw);
|
|
288
|
-
if (applyPatchToolMode) {
|
|
289
|
-
normalized.metadata = normalized.metadata || {};
|
|
290
|
-
const rt = ensureRuntimeMetadata(normalized.metadata);
|
|
291
|
-
rt.applyPatchToolMode = applyPatchToolMode;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
catch {
|
|
295
|
-
// best-effort: do not block request handling due to tool scan failures
|
|
296
|
-
}
|
|
297
413
|
if (isCompactionRequest(rawRequest)) {
|
|
298
414
|
normalized.metadata = normalized.metadata || {};
|
|
299
415
|
const rt = ensureRuntimeMetadata(normalized.metadata);
|
|
@@ -303,7 +419,7 @@ export class HubPipeline {
|
|
|
303
419
|
const shadowCompareBaselineMode = normalized.shadowCompare?.baselineMode;
|
|
304
420
|
const inboundAdapterContext = this.buildAdapterContext(normalized);
|
|
305
421
|
const inboundRecorder = this.maybeCreateStageRecorder(inboundAdapterContext, normalized.entryEndpoint, {
|
|
306
|
-
disableSnapshots: normalized.disableSnapshots === true
|
|
422
|
+
disableSnapshots: normalized.disableSnapshots === true,
|
|
307
423
|
});
|
|
308
424
|
const inboundStart = Date.now();
|
|
309
425
|
// Phase 0: observe client inbound payload violations (best-effort; no rewrites).
|
|
@@ -311,76 +427,65 @@ export class HubPipeline {
|
|
|
311
427
|
policy: effectivePolicy,
|
|
312
428
|
providerProtocol: this.resolveClientProtocol(normalized.entryEndpoint),
|
|
313
429
|
payload: rawRequest,
|
|
314
|
-
phase:
|
|
430
|
+
phase: "client_inbound",
|
|
315
431
|
stageRecorder: inboundRecorder,
|
|
316
|
-
requestId: normalized.id
|
|
432
|
+
requestId: normalized.id,
|
|
317
433
|
});
|
|
318
|
-
const formatEnvelope = await runReqInboundStage1FormatParse({
|
|
434
|
+
const formatEnvelope = await measureHubStage(normalized.id, "request_stage.req_inbound.format_parse", () => runReqInboundStage1FormatParse({
|
|
319
435
|
rawRequest,
|
|
320
436
|
adapterContext: inboundAdapterContext,
|
|
321
|
-
stageRecorder: inboundRecorder
|
|
322
|
-
});
|
|
323
|
-
const responsesResumeFromMetadata = normalized.metadata
|
|
324
|
-
|
|
325
|
-
: undefined;
|
|
326
|
-
const inboundStage2 = await runReqInboundStage2SemanticMap({
|
|
437
|
+
stageRecorder: inboundRecorder,
|
|
438
|
+
}));
|
|
439
|
+
const responsesResumeFromMetadata = readResponsesResumeFromMetadata(normalized.metadata);
|
|
440
|
+
const inboundStage2 = await measureHubStage(normalized.id, "request_stage.req_inbound.semantic_map", () => runReqInboundStage2SemanticMap({
|
|
327
441
|
adapterContext: inboundAdapterContext,
|
|
328
442
|
formatEnvelope,
|
|
329
443
|
semanticMapper,
|
|
330
|
-
...(responsesResumeFromMetadata
|
|
331
|
-
|
|
332
|
-
|
|
444
|
+
...(responsesResumeFromMetadata
|
|
445
|
+
? { responsesResume: responsesResumeFromMetadata }
|
|
446
|
+
: {}),
|
|
447
|
+
stageRecorder: inboundRecorder,
|
|
448
|
+
}));
|
|
333
449
|
// responsesResume must not enter chat_process as metadata; it is lifted into chat.semantics in stage2.
|
|
334
450
|
if (responsesResumeFromMetadata &&
|
|
335
451
|
normalized.metadata &&
|
|
336
|
-
Object.prototype.hasOwnProperty.call(normalized.metadata,
|
|
452
|
+
Object.prototype.hasOwnProperty.call(normalized.metadata, "responsesResume")) {
|
|
337
453
|
delete normalized.metadata.responsesResume;
|
|
338
454
|
}
|
|
339
|
-
const contextSnapshot = await hooks.captureContext({
|
|
455
|
+
const contextSnapshot = await measureHubStage(normalized.id, "request_stage.req_inbound.context_capture", () => hooks.captureContext({
|
|
340
456
|
rawRequest,
|
|
341
457
|
adapterContext: inboundAdapterContext,
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const rt = readRuntimeMetadata(normalized.metadata);
|
|
347
|
-
const mode = String(rt?.applyPatchToolMode || '').trim().toLowerCase();
|
|
348
|
-
if (mode === 'freeform' || mode === 'schema') {
|
|
349
|
-
standardizedRequest.metadata.applyPatchToolMode = mode;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// best-effort: do not block request handling due to metadata propagation failures
|
|
354
|
-
}
|
|
458
|
+
chatEnvelope: inboundStage2.chatEnvelope,
|
|
459
|
+
stageRecorder: inboundRecorder,
|
|
460
|
+
}));
|
|
461
|
+
let standardizedRequest = applyChatProcessEntryMediaCleanup(inboundStage2.standardizedRequest);
|
|
355
462
|
const activeProcessMode = resolveActiveProcessMode(normalized.processMode, standardizedRequest.messages);
|
|
356
463
|
if (activeProcessMode !== normalized.processMode) {
|
|
357
464
|
normalized.processMode = activeProcessMode;
|
|
358
|
-
normalized.metadata = normalized.metadata || {};
|
|
359
|
-
normalized.metadata.processMode = activeProcessMode;
|
|
360
465
|
}
|
|
361
|
-
const passthroughAudit = activeProcessMode ===
|
|
466
|
+
const passthroughAudit = activeProcessMode === "passthrough"
|
|
362
467
|
? buildPassthroughAudit(rawRequest, normalized.providerProtocol)
|
|
363
468
|
: undefined;
|
|
364
469
|
const inboundEnd = Date.now();
|
|
365
470
|
const nodeResults = [];
|
|
366
471
|
nodeResults.push({
|
|
367
|
-
id:
|
|
472
|
+
id: "req_inbound",
|
|
368
473
|
success: true,
|
|
369
474
|
metadata: {
|
|
370
|
-
node:
|
|
475
|
+
node: "req_inbound",
|
|
371
476
|
executionTime: inboundEnd - inboundStart,
|
|
372
477
|
startTime: inboundStart,
|
|
373
478
|
endTime: inboundEnd,
|
|
374
479
|
dataProcessed: {
|
|
375
480
|
messages: standardizedRequest.messages.length,
|
|
376
|
-
tools: standardizedRequest.tools?.length ?? 0
|
|
377
|
-
}
|
|
378
|
-
}
|
|
481
|
+
tools: standardizedRequest.tools?.length ?? 0,
|
|
482
|
+
},
|
|
483
|
+
},
|
|
379
484
|
});
|
|
380
485
|
// 将 VirtualRouter 层的 servertool 相关配置注入到 metadata,保证响应侧
|
|
381
486
|
// servertool(第三跳 reenter)也能访问到相同配置,即使当前 route 标记为 passthrough。
|
|
382
487
|
const metaBase = {
|
|
383
|
-
...(normalized.metadata ?? {})
|
|
488
|
+
...(normalized.metadata ?? {}),
|
|
384
489
|
};
|
|
385
490
|
const rtBase = ensureRuntimeMetadata(metaBase);
|
|
386
491
|
const webSearchConfig = this.config.virtualRouter?.webSearch;
|
|
@@ -397,54 +502,102 @@ export class HubPipeline {
|
|
|
397
502
|
}
|
|
398
503
|
normalized.metadata = metaBase;
|
|
399
504
|
let processedRequest;
|
|
400
|
-
if (activeProcessMode !==
|
|
401
|
-
assertNoMappableSemanticsInMetadata(metaBase,
|
|
402
|
-
const processResult = await runReqProcessStage1ToolGovernance({
|
|
505
|
+
if (activeProcessMode !== "passthrough") {
|
|
506
|
+
assertNoMappableSemanticsInMetadata(metaBase, "chat_process.request.entry");
|
|
507
|
+
const processResult = await measureHubStage(normalized.id, "request_stage.req_process.tool_governance", () => runReqProcessStage1ToolGovernance({
|
|
403
508
|
request: standardizedRequest,
|
|
404
509
|
rawPayload: rawRequest,
|
|
405
510
|
metadata: metaBase,
|
|
406
511
|
entryEndpoint: normalized.entryEndpoint,
|
|
407
512
|
requestId: normalized.id,
|
|
408
|
-
|
|
409
|
-
|
|
513
|
+
applyPatchToolMode: normalized.applyPatchToolMode,
|
|
514
|
+
stageRecorder: inboundRecorder,
|
|
515
|
+
}));
|
|
410
516
|
processedRequest = processResult.processedRequest;
|
|
411
517
|
// Surface request-side clock reservation into pipeline metadata so response conversion
|
|
412
518
|
// can commit delivery only after a successful response is produced.
|
|
413
519
|
try {
|
|
414
|
-
const reservation = processedRequest?.metadata
|
|
415
|
-
|
|
416
|
-
|
|
520
|
+
const reservation = processedRequest?.metadata
|
|
521
|
+
?.__clockReservation;
|
|
522
|
+
if (reservation && typeof reservation === "object") {
|
|
523
|
+
metaBase.__clockReservation =
|
|
524
|
+
reservation;
|
|
417
525
|
}
|
|
418
526
|
}
|
|
419
527
|
catch {
|
|
420
528
|
// best-effort: do not block request handling due to metadata propagation failures
|
|
421
529
|
}
|
|
422
530
|
if (processResult.nodeResult) {
|
|
423
|
-
nodeResults.push(this.convertProcessNodeResult(
|
|
531
|
+
nodeResults.push(this.convertProcessNodeResult("chat_process.req.stage4.tool_governance", processResult.nodeResult));
|
|
424
532
|
}
|
|
425
533
|
}
|
|
426
534
|
else {
|
|
427
535
|
nodeResults.push({
|
|
428
|
-
id:
|
|
536
|
+
id: "chat_process.req.stage4.tool_governance",
|
|
429
537
|
success: true,
|
|
430
538
|
metadata: {
|
|
431
|
-
node:
|
|
539
|
+
node: "chat_process.req.stage4.tool_governance",
|
|
432
540
|
skipped: true,
|
|
433
|
-
reason:
|
|
434
|
-
}
|
|
541
|
+
reason: "process_mode_passthrough_parse_record_only",
|
|
542
|
+
},
|
|
435
543
|
});
|
|
436
544
|
if (passthroughAudit) {
|
|
437
545
|
annotatePassthroughGovernanceSkip(passthroughAudit);
|
|
438
546
|
}
|
|
439
547
|
}
|
|
440
|
-
let workingRequest = processedRequest ?? standardizedRequest;
|
|
548
|
+
let workingRequest = syncResponsesContextFromCanonicalMessages(processedRequest ?? standardizedRequest);
|
|
549
|
+
const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
|
|
550
|
+
if (sessionIdentifiers.sessionId &&
|
|
551
|
+
normalized.metadata &&
|
|
552
|
+
typeof normalized.metadata === "object") {
|
|
553
|
+
normalized.metadata.sessionId =
|
|
554
|
+
sessionIdentifiers.sessionId;
|
|
555
|
+
}
|
|
556
|
+
if (sessionIdentifiers.conversationId &&
|
|
557
|
+
normalized.metadata &&
|
|
558
|
+
typeof normalized.metadata === "object") {
|
|
559
|
+
normalized.metadata.conversationId =
|
|
560
|
+
sessionIdentifiers.conversationId;
|
|
561
|
+
}
|
|
441
562
|
// 使用与 VirtualRouter 一致的 tiktoken 计数逻辑,对标准化请求进行一次
|
|
442
563
|
// 上下文 token 估算,供后续 usage 归一化与统计使用。
|
|
443
564
|
try {
|
|
444
|
-
const
|
|
445
|
-
|
|
565
|
+
const sessionEstimate = await measureHubStage(normalized.id, "request_stage.req_process.token_estimate.session_delta", () => estimateChatProcessSessionInputTokensDetailed({
|
|
566
|
+
sessionId: sessionIdentifiers.sessionId,
|
|
567
|
+
conversationId: sessionIdentifiers.conversationId,
|
|
568
|
+
}, workingRequest), {
|
|
569
|
+
mapCompletedDetails: (value) => ({
|
|
570
|
+
forceLog: value.mode === "unavailable" ||
|
|
571
|
+
value.mode === "session_reuse" ||
|
|
572
|
+
value.mode === "session_delta",
|
|
573
|
+
scope: value.scope,
|
|
574
|
+
mode: value.mode,
|
|
575
|
+
reason: value.reason,
|
|
576
|
+
previousMessageCount: value.previousMessageCount,
|
|
577
|
+
appendedMessageCount: value.appendedMessageCount,
|
|
578
|
+
hasPreviousTokens: value.hasPreviousTokens,
|
|
579
|
+
hasPreviousMessageCount: value.hasPreviousMessageCount,
|
|
580
|
+
hasToolsSignature: value.hasToolsSignature,
|
|
581
|
+
hasParametersSignature: value.hasParametersSignature,
|
|
582
|
+
previousParametersSignatureDigest: value.previousParametersSignatureDigest,
|
|
583
|
+
currentParametersSignatureDigest: value.currentParametersSignatureDigest,
|
|
584
|
+
}),
|
|
585
|
+
});
|
|
586
|
+
const estimatedTokens = typeof sessionEstimate.tokens === "number" &&
|
|
587
|
+
Number.isFinite(sessionEstimate.tokens) &&
|
|
588
|
+
sessionEstimate.tokens > 0
|
|
589
|
+
? sessionEstimate.tokens
|
|
590
|
+
: await measureHubStage(normalized.id, "request_stage.req_process.token_estimate.full_count", () => computeRequestTokens(workingRequest, ""));
|
|
591
|
+
if (typeof estimatedTokens === "number" &&
|
|
592
|
+
Number.isFinite(estimatedTokens) &&
|
|
593
|
+
estimatedTokens > 0) {
|
|
446
594
|
normalized.metadata = normalized.metadata || {};
|
|
447
|
-
normalized.metadata.estimatedInputTokens =
|
|
595
|
+
normalized.metadata.estimatedInputTokens =
|
|
596
|
+
estimatedTokens;
|
|
597
|
+
saveChatProcessSessionInputEstimate({
|
|
598
|
+
sessionId: sessionIdentifiers.sessionId,
|
|
599
|
+
conversationId: sessionIdentifiers.conversationId,
|
|
600
|
+
}, workingRequest, estimatedTokens);
|
|
448
601
|
}
|
|
449
602
|
}
|
|
450
603
|
catch {
|
|
@@ -453,37 +606,19 @@ export class HubPipeline {
|
|
|
453
606
|
const normalizedMeta = normalized.metadata;
|
|
454
607
|
// responsesResume is a client-protocol semantic (/v1/responses tool loop) and must live in chat.semantics.
|
|
455
608
|
// Do not read it from metadata once entering chat_process.
|
|
456
|
-
const responsesResume = (
|
|
457
|
-
try {
|
|
458
|
-
const semantics = workingRequest?.semantics;
|
|
459
|
-
const node = semantics && typeof semantics === 'object' && !Array.isArray(semantics) ? semantics.responses : undefined;
|
|
460
|
-
const resume = node && typeof node === 'object' && !Array.isArray(node) ? node.resume : undefined;
|
|
461
|
-
return resume && typeof resume === 'object' && !Array.isArray(resume) ? resume : undefined;
|
|
462
|
-
}
|
|
463
|
-
catch {
|
|
464
|
-
return undefined;
|
|
465
|
-
}
|
|
466
|
-
})();
|
|
609
|
+
const responsesResume = readResponsesResumeFromRequestSemantics(workingRequest);
|
|
467
610
|
const stdMetadata = workingRequest?.metadata;
|
|
468
|
-
const hasImageAttachment = (
|
|
469
|
-
(normalizedMeta?.hasImageAttachment === true || normalizedMeta?.hasImageAttachment === 'true');
|
|
611
|
+
const hasImageAttachment = containsImageAttachment((workingRequest.messages ?? []));
|
|
470
612
|
const serverToolRequired = stdMetadata?.webSearchEnabled === true ||
|
|
471
613
|
stdMetadata?.serverToolRequired === true;
|
|
472
|
-
const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
|
|
473
|
-
// 将从 metadata / clientHeaders 中解析出的会话标识同步回 normalized.metadata,
|
|
474
|
-
// 便于后续 AdapterContext(响应侧 servertool)也能访问到相同的 sessionId /
|
|
475
|
-
// conversationId,用于 sticky-session 相关逻辑(例如 stopMessage)。
|
|
476
|
-
if (sessionIdentifiers.sessionId && normalized.metadata && typeof normalized.metadata === 'object') {
|
|
477
|
-
normalized.metadata.sessionId = sessionIdentifiers.sessionId;
|
|
478
|
-
}
|
|
479
|
-
if (sessionIdentifiers.conversationId && normalized.metadata && typeof normalized.metadata === 'object') {
|
|
480
|
-
normalized.metadata.conversationId = sessionIdentifiers.conversationId;
|
|
481
|
-
}
|
|
482
614
|
const disableStickyRoutes = readRuntimeMetadata(normalized.metadata)?.disableStickyRoutes === true;
|
|
483
615
|
const stopMessageRouterMetadata = resolveStopMessageRouterMetadata(normalized.metadata);
|
|
484
616
|
const estimatedInputTokens = (() => {
|
|
485
|
-
const value = normalized.metadata
|
|
486
|
-
|
|
617
|
+
const value = normalized.metadata
|
|
618
|
+
?.estimatedInputTokens;
|
|
619
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
620
|
+
? value
|
|
621
|
+
: undefined;
|
|
487
622
|
})();
|
|
488
623
|
const metadataInput = {
|
|
489
624
|
requestId: normalized.id,
|
|
@@ -498,36 +633,35 @@ export class HubPipeline {
|
|
|
498
633
|
...(estimatedInputTokens !== undefined ? { estimatedInputTokens } : {}),
|
|
499
634
|
...(disableStickyRoutes ? { disableStickyRoutes: true } : {}),
|
|
500
635
|
...(serverToolRequired ? { serverToolRequired: true } : {}),
|
|
501
|
-
...(sessionIdentifiers.sessionId
|
|
502
|
-
|
|
503
|
-
|
|
636
|
+
...(sessionIdentifiers.sessionId
|
|
637
|
+
? { sessionId: sessionIdentifiers.sessionId }
|
|
638
|
+
: {}),
|
|
639
|
+
...(sessionIdentifiers.conversationId
|
|
640
|
+
? { conversationId: sessionIdentifiers.conversationId }
|
|
641
|
+
: {}),
|
|
642
|
+
...stopMessageRouterMetadata,
|
|
504
643
|
};
|
|
505
|
-
const routing = runReqProcessStage2RouteSelect({
|
|
644
|
+
const routing = await measureHubStage(normalized.id, "request_stage.req_process.route_select", () => runReqProcessStage2RouteSelect({
|
|
506
645
|
routerEngine: this.routerEngine,
|
|
507
646
|
request: workingRequest,
|
|
508
647
|
metadataInput,
|
|
509
648
|
normalizedMetadata: normalized.metadata,
|
|
510
|
-
stageRecorder: inboundRecorder
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if ((stopMessageState || preCommandState) && normalized.metadata && typeof normalized.metadata === 'object') {
|
|
515
|
-
const rt = ensureRuntimeMetadata(normalized.metadata);
|
|
516
|
-
if (stopMessageState) {
|
|
517
|
-
rt.stopMessageState = stopMessageState;
|
|
518
|
-
}
|
|
519
|
-
if (preCommandState) {
|
|
520
|
-
rt.preCommandState = preCommandState;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
649
|
+
stageRecorder: inboundRecorder,
|
|
650
|
+
}));
|
|
651
|
+
this.routerEngine.getStopMessageState(metadataInput);
|
|
652
|
+
this.routerEngine.getPreCommandState(metadataInput);
|
|
523
653
|
// Emit virtual router hit log for debugging (orange [virtual-router] ...)
|
|
524
654
|
try {
|
|
525
655
|
const routeName = routing.decision?.routeName;
|
|
526
656
|
const providerKey = routing.target?.providerKey;
|
|
527
657
|
const modelId = workingRequest.model;
|
|
528
|
-
const logger = (normalized.metadata &&
|
|
529
|
-
|
|
530
|
-
|
|
658
|
+
const logger = (normalized.metadata &&
|
|
659
|
+
normalized.metadata.logger);
|
|
660
|
+
if (logger &&
|
|
661
|
+
typeof logger.logVirtualRouterHit === "function" &&
|
|
662
|
+
routeName &&
|
|
663
|
+
providerKey) {
|
|
664
|
+
logger.logVirtualRouterHit(routeName, providerKey, typeof modelId === "string" ? modelId : undefined);
|
|
531
665
|
}
|
|
532
666
|
}
|
|
533
667
|
catch {
|
|
@@ -538,24 +672,26 @@ export class HubPipeline {
|
|
|
538
672
|
this.applyMaxTokensPolicy(workingRequest, routing.target);
|
|
539
673
|
const outboundAdapterContext = this.buildAdapterContext(normalized, routing.target);
|
|
540
674
|
if (routing.target?.compatibilityProfile) {
|
|
541
|
-
outboundAdapterContext.compatibilityProfile =
|
|
675
|
+
outboundAdapterContext.compatibilityProfile =
|
|
676
|
+
routing.target.compatibilityProfile;
|
|
542
677
|
}
|
|
543
678
|
const outboundProtocol = outboundAdapterContext.providerProtocol;
|
|
544
|
-
if (activeProcessMode ===
|
|
679
|
+
if (activeProcessMode === "passthrough" &&
|
|
680
|
+
outboundProtocol !== normalized.providerProtocol) {
|
|
545
681
|
throw new Error(`[HubPipeline] passthrough requires matching protocols: entry=${normalized.providerProtocol}, target=${outboundProtocol}`);
|
|
546
682
|
}
|
|
547
683
|
// Snapshots must be grouped by entry endpoint (client-facing protocol), not by provider protocol.
|
|
548
684
|
// Otherwise one request would be split across multiple folders (e.g. openai-responses + anthropic-messages),
|
|
549
685
|
// which breaks codex-samples correlation.
|
|
550
686
|
const outboundRecorder = this.maybeCreateStageRecorder(outboundAdapterContext, normalized.entryEndpoint, {
|
|
551
|
-
disableSnapshots: normalized.disableSnapshots === true
|
|
687
|
+
disableSnapshots: normalized.disableSnapshots === true,
|
|
552
688
|
});
|
|
553
689
|
const outboundStart = Date.now();
|
|
554
690
|
let providerPayload;
|
|
555
691
|
let shadowBaselineProviderPayload;
|
|
556
|
-
if (activeProcessMode ===
|
|
692
|
+
if (activeProcessMode === "passthrough") {
|
|
557
693
|
providerPayload = jsonClone(rawRequest);
|
|
558
|
-
if (typeof outboundStream ===
|
|
694
|
+
if (typeof outboundStream === "boolean") {
|
|
559
695
|
providerPayload.stream = outboundStream;
|
|
560
696
|
}
|
|
561
697
|
if (passthroughAudit) {
|
|
@@ -564,57 +700,65 @@ export class HubPipeline {
|
|
|
564
700
|
}
|
|
565
701
|
else {
|
|
566
702
|
const protocolSwitch = outboundProtocol !== normalized.providerProtocol;
|
|
567
|
-
const outboundHooks = protocolSwitch
|
|
703
|
+
const outboundHooks = protocolSwitch
|
|
704
|
+
? this.resolveProtocolHooks(outboundProtocol)
|
|
705
|
+
: hooks;
|
|
568
706
|
if (!outboundHooks) {
|
|
569
707
|
throw new Error(`[HubPipeline] Unsupported provider protocol for hub pipeline: ${outboundProtocol}`);
|
|
570
708
|
}
|
|
571
|
-
const outboundSemanticMapper = protocolSwitch
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
709
|
+
const outboundSemanticMapper = protocolSwitch
|
|
710
|
+
? outboundHooks.createSemanticMapper()
|
|
711
|
+
: semanticMapper;
|
|
712
|
+
const outboundContextMetadataKey = protocolSwitch
|
|
713
|
+
? outboundHooks.contextMetadataKey
|
|
714
|
+
: hooks.contextMetadataKey;
|
|
715
|
+
const outboundContextSnapshot = protocolSwitch
|
|
716
|
+
? undefined
|
|
717
|
+
: contextSnapshot;
|
|
718
|
+
const outboundStage1 = await measureHubStage(normalized.id, "request_stage.req_outbound.semantic_map", () => runReqOutboundStage1SemanticMap({
|
|
575
719
|
request: workingRequest,
|
|
576
720
|
adapterContext: outboundAdapterContext,
|
|
577
721
|
semanticMapper: outboundSemanticMapper,
|
|
578
722
|
contextSnapshot: outboundContextSnapshot,
|
|
579
723
|
contextMetadataKey: outboundContextMetadataKey,
|
|
580
|
-
stageRecorder: outboundRecorder
|
|
581
|
-
});
|
|
582
|
-
let formattedPayload = await runReqOutboundStage2FormatBuild({
|
|
724
|
+
stageRecorder: outboundRecorder,
|
|
725
|
+
}));
|
|
726
|
+
let formattedPayload = await measureHubStage(normalized.id, "request_stage.req_outbound.format_build", () => runReqOutboundStage2FormatBuild({
|
|
583
727
|
formatEnvelope: outboundStage1.formatEnvelope,
|
|
584
|
-
stageRecorder: outboundRecorder
|
|
585
|
-
});
|
|
586
|
-
formattedPayload = await runReqOutboundStage3Compat({
|
|
728
|
+
stageRecorder: outboundRecorder,
|
|
729
|
+
}));
|
|
730
|
+
formattedPayload = await measureHubStage(normalized.id, "request_stage.req_outbound.compat", () => runReqOutboundStage3Compat({
|
|
587
731
|
payload: formattedPayload,
|
|
588
732
|
adapterContext: outboundAdapterContext,
|
|
589
|
-
stageRecorder: outboundRecorder
|
|
590
|
-
});
|
|
733
|
+
stageRecorder: outboundRecorder,
|
|
734
|
+
}));
|
|
591
735
|
if (shadowCompareBaselineMode) {
|
|
592
736
|
const baselinePolicy = {
|
|
593
737
|
...(effectivePolicy ?? {}),
|
|
594
|
-
mode: shadowCompareBaselineMode
|
|
738
|
+
mode: shadowCompareBaselineMode,
|
|
595
739
|
};
|
|
596
740
|
// Compute a baseline provider payload in the *same execution*, without recording
|
|
597
741
|
// snapshots/diffs and without re-running the full pipeline. This avoids side effects
|
|
598
742
|
// (conversation store, followup captures, etc.) that a second execute() would trigger.
|
|
599
|
-
const baselineFormatted = typeof globalThis.structuredClone ===
|
|
743
|
+
const baselineFormatted = typeof globalThis.structuredClone === "function"
|
|
600
744
|
? globalThis.structuredClone(formattedPayload)
|
|
601
745
|
: jsonClone(formattedPayload);
|
|
602
746
|
let baselinePayload = applyHubProviderOutboundPolicy({
|
|
603
747
|
policy: baselinePolicy,
|
|
604
748
|
providerProtocol: outboundProtocol,
|
|
605
|
-
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile ===
|
|
749
|
+
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
606
750
|
? outboundAdapterContext.compatibilityProfile
|
|
607
751
|
: undefined,
|
|
608
752
|
payload: baselineFormatted,
|
|
609
753
|
stageRecorder: undefined,
|
|
610
|
-
requestId: normalized.id
|
|
754
|
+
requestId: normalized.id,
|
|
611
755
|
});
|
|
612
756
|
baselinePayload = applyProviderOutboundToolSurface({
|
|
613
757
|
config: this.config.toolSurface,
|
|
614
758
|
providerProtocol: outboundProtocol,
|
|
615
759
|
payload: baselinePayload,
|
|
616
760
|
stageRecorder: undefined,
|
|
617
|
-
requestId: normalized.id
|
|
761
|
+
requestId: normalized.id,
|
|
618
762
|
});
|
|
619
763
|
shadowBaselineProviderPayload = baselinePayload;
|
|
620
764
|
}
|
|
@@ -623,40 +767,40 @@ export class HubPipeline {
|
|
|
623
767
|
recordHubPolicyObservation({
|
|
624
768
|
policy: effectivePolicy,
|
|
625
769
|
providerProtocol: outboundProtocol,
|
|
626
|
-
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile ===
|
|
770
|
+
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
627
771
|
? outboundAdapterContext.compatibilityProfile
|
|
628
772
|
: undefined,
|
|
629
773
|
payload: formattedPayload,
|
|
630
774
|
stageRecorder: outboundRecorder,
|
|
631
|
-
requestId: normalized.id
|
|
775
|
+
requestId: normalized.id,
|
|
632
776
|
});
|
|
633
|
-
providerPayload = applyHubProviderOutboundPolicy({
|
|
777
|
+
providerPayload = (await measureHubStage(normalized.id, "request_stage.req_outbound.provider_policy", () => applyHubProviderOutboundPolicy({
|
|
634
778
|
policy: effectivePolicy,
|
|
635
779
|
providerProtocol: outboundProtocol,
|
|
636
|
-
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile ===
|
|
780
|
+
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
637
781
|
? outboundAdapterContext.compatibilityProfile
|
|
638
782
|
: undefined,
|
|
639
783
|
payload: formattedPayload,
|
|
640
784
|
stageRecorder: outboundRecorder,
|
|
641
|
-
requestId: normalized.id
|
|
642
|
-
});
|
|
643
|
-
providerPayload = applyProviderOutboundToolSurface({
|
|
785
|
+
requestId: normalized.id,
|
|
786
|
+
})));
|
|
787
|
+
providerPayload = (await measureHubStage(normalized.id, "request_stage.req_outbound.tool_surface", () => applyProviderOutboundToolSurface({
|
|
644
788
|
config: this.config.toolSurface,
|
|
645
789
|
providerProtocol: outboundProtocol,
|
|
646
790
|
payload: providerPayload,
|
|
647
791
|
stageRecorder: outboundRecorder,
|
|
648
|
-
requestId: normalized.id
|
|
649
|
-
});
|
|
792
|
+
requestId: normalized.id,
|
|
793
|
+
})));
|
|
650
794
|
providerPayload = maybeApplyDirectBuiltinWebSearchTool(providerPayload, outboundAdapterContext, outboundProtocol);
|
|
651
795
|
recordHubPolicyObservation({
|
|
652
796
|
policy: effectivePolicy,
|
|
653
797
|
providerProtocol: outboundProtocol,
|
|
654
|
-
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile ===
|
|
798
|
+
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
655
799
|
? outboundAdapterContext.compatibilityProfile
|
|
656
800
|
: undefined,
|
|
657
801
|
payload: providerPayload,
|
|
658
802
|
stageRecorder: outboundRecorder,
|
|
659
|
-
requestId: normalized.id
|
|
803
|
+
requestId: normalized.id,
|
|
660
804
|
});
|
|
661
805
|
if (passthroughAudit) {
|
|
662
806
|
attachPassthroughProviderInputAudit(passthroughAudit, providerPayload, outboundProtocol);
|
|
@@ -664,25 +808,25 @@ export class HubPipeline {
|
|
|
664
808
|
}
|
|
665
809
|
const outboundEnd = Date.now();
|
|
666
810
|
nodeResults.push({
|
|
667
|
-
id:
|
|
811
|
+
id: "req_outbound",
|
|
668
812
|
success: true,
|
|
669
813
|
metadata: {
|
|
670
|
-
node:
|
|
814
|
+
node: "req_outbound",
|
|
671
815
|
executionTime: outboundEnd - outboundStart,
|
|
672
816
|
startTime: outboundStart,
|
|
673
817
|
endTime: outboundEnd,
|
|
674
818
|
dataProcessed: {
|
|
675
819
|
messages: workingRequest.messages.length,
|
|
676
|
-
tools: workingRequest.tools?.length ?? 0
|
|
677
|
-
}
|
|
678
|
-
}
|
|
820
|
+
tools: workingRequest.tools?.length ?? 0,
|
|
821
|
+
},
|
|
822
|
+
},
|
|
679
823
|
});
|
|
680
824
|
// 为响应侧 servertool/web_search 提供一次性 Chat 请求快照,便于在 Hub 内部实现
|
|
681
825
|
// 第三跳(将工具结果注入消息历史后重新调用主模型)。
|
|
682
826
|
//
|
|
683
827
|
// 注意:这里不再根据 processMode(passthrough/chat) 做分支判断——即使某些
|
|
684
828
|
// route 将 processMode 标记为 passthrough,我们仍然需要保留一次规范化后的
|
|
685
|
-
// Chat 请求快照,供 stopMessage
|
|
829
|
+
// Chat 请求快照,供 stopMessage 等被动触发型
|
|
686
830
|
// servertool 在响应阶段使用。
|
|
687
831
|
//
|
|
688
832
|
// 之前这里通过 JSON.stringify/parse 做深拷贝,但在部分 Responses/Gemini
|
|
@@ -698,12 +842,15 @@ export class HubPipeline {
|
|
|
698
842
|
const capturedChatRequest = {
|
|
699
843
|
model: workingRequest.model,
|
|
700
844
|
messages: jsonClone(workingRequest.messages),
|
|
701
|
-
tools: workingRequest.tools
|
|
702
|
-
|
|
845
|
+
tools: workingRequest.tools
|
|
846
|
+
? jsonClone(workingRequest.tools)
|
|
847
|
+
: workingRequest.tools,
|
|
848
|
+
parameters: workingRequest.parameters
|
|
849
|
+
? jsonClone(workingRequest.parameters)
|
|
850
|
+
: workingRequest.parameters,
|
|
703
851
|
};
|
|
704
852
|
const metadata = {
|
|
705
853
|
...normalized.metadata,
|
|
706
|
-
...(hasImageAttachment ? { hasImageAttachment: true } : {}),
|
|
707
854
|
capturedChatRequest,
|
|
708
855
|
entryEndpoint: normalized.entryEndpoint,
|
|
709
856
|
providerProtocol: outboundProtocol,
|
|
@@ -712,18 +859,26 @@ export class HubPipeline {
|
|
|
712
859
|
...(passthroughAudit ? { passthroughAudit } : {}),
|
|
713
860
|
routeHint: normalized.routeHint,
|
|
714
861
|
target: routing.target,
|
|
715
|
-
...(typeof outboundStream ===
|
|
862
|
+
...(typeof outboundStream === "boolean"
|
|
863
|
+
? { providerStream: outboundStream }
|
|
864
|
+
: {}),
|
|
716
865
|
...(shadowBaselineProviderPayload
|
|
717
866
|
? {
|
|
718
867
|
hubShadowCompare: {
|
|
719
868
|
baselineMode: shadowCompareBaselineMode,
|
|
720
|
-
candidateMode: (effectivePolicy?.mode ??
|
|
869
|
+
candidateMode: (effectivePolicy?.mode ?? "off"),
|
|
721
870
|
providerProtocol: outboundProtocol,
|
|
722
|
-
baselineProviderPayload: shadowBaselineProviderPayload
|
|
723
|
-
}
|
|
871
|
+
baselineProviderPayload: shadowBaselineProviderPayload,
|
|
872
|
+
},
|
|
724
873
|
}
|
|
725
|
-
: {})
|
|
874
|
+
: {}),
|
|
726
875
|
};
|
|
876
|
+
if (hasImageAttachment) {
|
|
877
|
+
metadata.hasImageAttachment = true;
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
delete metadata.hasImageAttachment;
|
|
881
|
+
}
|
|
727
882
|
return {
|
|
728
883
|
requestId: normalized.id,
|
|
729
884
|
providerPayload,
|
|
@@ -733,33 +888,47 @@ export class HubPipeline {
|
|
|
733
888
|
routingDiagnostics: routing.diagnostics,
|
|
734
889
|
target: routing.target,
|
|
735
890
|
metadata,
|
|
736
|
-
nodeResults
|
|
891
|
+
nodeResults,
|
|
737
892
|
};
|
|
738
893
|
}
|
|
739
894
|
resolveClientProtocol(entryEndpoint) {
|
|
740
895
|
const protocol = resolveHubClientProtocolWithNative(entryEndpoint);
|
|
741
|
-
if (protocol ===
|
|
896
|
+
if (protocol === "openai-responses" ||
|
|
897
|
+
protocol === "anthropic-messages" ||
|
|
898
|
+
protocol === "openai-chat") {
|
|
742
899
|
return protocol;
|
|
743
900
|
}
|
|
744
|
-
return
|
|
901
|
+
return "openai-chat";
|
|
745
902
|
}
|
|
746
903
|
coerceStandardizedRequestFromPayload(payload, normalized) {
|
|
747
|
-
const model = typeof payload.model ===
|
|
904
|
+
const model = typeof payload.model === "string" && payload.model.trim().length
|
|
905
|
+
? payload.model.trim()
|
|
906
|
+
: "";
|
|
748
907
|
if (!model) {
|
|
749
|
-
throw new Error(
|
|
908
|
+
throw new Error("[HubPipeline] outbound stage requires payload.model");
|
|
750
909
|
}
|
|
751
|
-
const messages = Array.isArray(payload.messages)
|
|
910
|
+
const messages = Array.isArray(payload.messages)
|
|
911
|
+
? payload.messages
|
|
912
|
+
: null;
|
|
752
913
|
if (!messages) {
|
|
753
|
-
throw new Error(
|
|
914
|
+
throw new Error("[HubPipeline] outbound stage requires payload.messages[]");
|
|
754
915
|
}
|
|
755
|
-
const tools = Array.isArray(payload.tools)
|
|
756
|
-
|
|
916
|
+
const tools = Array.isArray(payload.tools)
|
|
917
|
+
? payload.tools
|
|
918
|
+
: undefined;
|
|
919
|
+
const parameters = payload.parameters &&
|
|
920
|
+
typeof payload.parameters === "object" &&
|
|
921
|
+
!Array.isArray(payload.parameters)
|
|
757
922
|
? payload.parameters
|
|
758
923
|
: {};
|
|
759
|
-
const semanticsFromPayload = payload.semantics &&
|
|
924
|
+
const semanticsFromPayload = payload.semantics &&
|
|
925
|
+
typeof payload.semantics === "object" &&
|
|
926
|
+
!Array.isArray(payload.semantics)
|
|
760
927
|
? jsonClone(payload.semantics)
|
|
761
928
|
: undefined;
|
|
762
|
-
const metadataFromPayload = payload.metadata &&
|
|
929
|
+
const metadataFromPayload = payload.metadata &&
|
|
930
|
+
typeof payload.metadata === "object" &&
|
|
931
|
+
!Array.isArray(payload.metadata)
|
|
763
932
|
? payload.metadata
|
|
764
933
|
: undefined;
|
|
765
934
|
const standardizedRequest = {
|
|
@@ -773,21 +942,28 @@ export class HubPipeline {
|
|
|
773
942
|
requestId: normalized.id,
|
|
774
943
|
stream: normalized.stream,
|
|
775
944
|
processMode: normalized.processMode,
|
|
776
|
-
...(normalized.routeHint ? { routeHint: normalized.routeHint } : {})
|
|
945
|
+
...(normalized.routeHint ? { routeHint: normalized.routeHint } : {}),
|
|
777
946
|
},
|
|
778
|
-
...(semanticsFromPayload
|
|
947
|
+
...(semanticsFromPayload
|
|
948
|
+
? { semantics: semanticsFromPayload }
|
|
949
|
+
: {}),
|
|
779
950
|
};
|
|
780
951
|
// Ensure followup/chat_process entry can still preserve mappable semantics
|
|
781
952
|
// without injecting them into metadata.
|
|
782
953
|
try {
|
|
783
|
-
const semantics = standardizedRequest.semantics &&
|
|
954
|
+
const semantics = standardizedRequest.semantics &&
|
|
955
|
+
typeof standardizedRequest.semantics === "object"
|
|
784
956
|
? standardizedRequest.semantics
|
|
785
957
|
: (standardizedRequest.semantics = {});
|
|
786
|
-
if (!semantics.tools ||
|
|
958
|
+
if (!semantics.tools ||
|
|
959
|
+
typeof semantics.tools !== "object" ||
|
|
960
|
+
Array.isArray(semantics.tools)) {
|
|
787
961
|
semantics.tools = {};
|
|
788
962
|
}
|
|
789
963
|
const toolsNode = semantics.tools;
|
|
790
|
-
if (Array.isArray(payload.tools) &&
|
|
964
|
+
if (Array.isArray(payload.tools) &&
|
|
965
|
+
payload.tools.length &&
|
|
966
|
+
toolsNode.clientToolsRaw === undefined) {
|
|
791
967
|
toolsNode.clientToolsRaw = jsonClone(payload.tools);
|
|
792
968
|
}
|
|
793
969
|
}
|
|
@@ -799,7 +975,7 @@ export class HubPipeline {
|
|
|
799
975
|
model,
|
|
800
976
|
messages,
|
|
801
977
|
...(tools ? { tools } : {}),
|
|
802
|
-
...(parameters && Object.keys(parameters).length ? { parameters } : {})
|
|
978
|
+
...(parameters && Object.keys(parameters).length ? { parameters } : {}),
|
|
803
979
|
};
|
|
804
980
|
return { standardizedRequest, rawPayload };
|
|
805
981
|
}
|
|
@@ -810,21 +986,21 @@ export class HubPipeline {
|
|
|
810
986
|
}
|
|
811
987
|
const nodeResults = [];
|
|
812
988
|
nodeResults.push({
|
|
813
|
-
id:
|
|
989
|
+
id: "req_inbound",
|
|
814
990
|
success: true,
|
|
815
991
|
metadata: {
|
|
816
|
-
node:
|
|
992
|
+
node: "req_inbound",
|
|
817
993
|
skipped: true,
|
|
818
|
-
reason:
|
|
819
|
-
dataProcessed: {}
|
|
820
|
-
}
|
|
994
|
+
reason: "stage=outbound",
|
|
995
|
+
dataProcessed: {},
|
|
996
|
+
},
|
|
821
997
|
});
|
|
822
998
|
const rawPayloadInput = this.asJsonObject(normalized.payload);
|
|
823
999
|
const { standardizedRequest: standardizedRequestBase, rawPayload } = this.coerceStandardizedRequestFromPayload(rawPayloadInput, normalized);
|
|
824
1000
|
// Keep metadata injection consistent with the inbound path: servertool/web_search config must be available
|
|
825
1001
|
// to chat-process/tool governance even when request enters at outbound stage.
|
|
826
1002
|
const metaBase = {
|
|
827
|
-
...(normalized.metadata ?? {})
|
|
1003
|
+
...(normalized.metadata ?? {}),
|
|
828
1004
|
};
|
|
829
1005
|
const rtBase = ensureRuntimeMetadata(metaBase);
|
|
830
1006
|
const webSearchConfig = this.config.virtualRouter?.webSearch;
|
|
@@ -840,100 +1016,123 @@ export class HubPipeline {
|
|
|
840
1016
|
rtBase.clock = clockConfig;
|
|
841
1017
|
}
|
|
842
1018
|
normalized.metadata = metaBase;
|
|
843
|
-
const
|
|
844
|
-
|
|
1019
|
+
const cleanedRequest = applyChatProcessEntryMediaCleanup(standardizedRequestBase);
|
|
1020
|
+
let standardizedRequest = cleanedRequest;
|
|
1021
|
+
const activeProcessMode = resolveActiveProcessMode(normalized.processMode, cleanedRequest.messages);
|
|
845
1022
|
if (activeProcessMode !== normalized.processMode) {
|
|
846
1023
|
normalized.processMode = activeProcessMode;
|
|
847
|
-
normalized.metadata = normalized.metadata || {};
|
|
848
|
-
normalized.metadata.processMode = activeProcessMode;
|
|
849
1024
|
}
|
|
850
|
-
const passthroughAudit = activeProcessMode ===
|
|
1025
|
+
const passthroughAudit = activeProcessMode === "passthrough"
|
|
851
1026
|
? buildPassthroughAudit(rawPayload, normalized.providerProtocol)
|
|
852
1027
|
: undefined;
|
|
853
1028
|
// Semantic Gate (chat_process entry): lift any mappable protocol semantics from metadata into request.semantics.
|
|
854
1029
|
// This is the last chance before entering chat_process; after this point we fail-fast on banned metadata keys.
|
|
855
1030
|
try {
|
|
856
|
-
|
|
857
|
-
? metaBase.responsesResume
|
|
858
|
-
: undefined;
|
|
859
|
-
if (resumeMeta) {
|
|
860
|
-
standardizedRequest.semantics = standardizedRequest.semantics ?? {};
|
|
861
|
-
const semantics = standardizedRequest.semantics;
|
|
862
|
-
if (!semantics.responses || typeof semantics.responses !== 'object' || Array.isArray(semantics.responses)) {
|
|
863
|
-
semantics.responses = {};
|
|
864
|
-
}
|
|
865
|
-
const responsesNode = semantics.responses;
|
|
866
|
-
if (responsesNode.resume === undefined) {
|
|
867
|
-
responsesNode.resume = jsonClone(resumeMeta);
|
|
868
|
-
}
|
|
869
|
-
delete metaBase.responsesResume;
|
|
870
|
-
}
|
|
1031
|
+
standardizedRequest = liftResponsesResumeIntoSemantics(standardizedRequest, metaBase);
|
|
871
1032
|
}
|
|
872
1033
|
catch {
|
|
873
1034
|
// best-effort; validation happens below
|
|
874
1035
|
}
|
|
875
|
-
try {
|
|
876
|
-
const rt = readRuntimeMetadata(metaBase);
|
|
877
|
-
const mode = String(rt?.applyPatchToolMode || '').trim().toLowerCase();
|
|
878
|
-
if (mode === 'freeform' || mode === 'schema') {
|
|
879
|
-
standardizedRequest.metadata.applyPatchToolMode = mode;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
catch {
|
|
883
|
-
// ignore
|
|
884
|
-
}
|
|
885
1036
|
const adapterContext = this.buildAdapterContext(normalized);
|
|
886
1037
|
const stageRecorder = this.maybeCreateStageRecorder(adapterContext, normalized.entryEndpoint, {
|
|
887
|
-
disableSnapshots: normalized.disableSnapshots === true
|
|
1038
|
+
disableSnapshots: normalized.disableSnapshots === true,
|
|
888
1039
|
});
|
|
889
1040
|
let processedRequest;
|
|
890
|
-
if (activeProcessMode !==
|
|
891
|
-
assertNoMappableSemanticsInMetadata(metaBase,
|
|
892
|
-
const processResult = await runReqProcessStage1ToolGovernance({
|
|
1041
|
+
if (activeProcessMode !== "passthrough") {
|
|
1042
|
+
assertNoMappableSemanticsInMetadata(metaBase, "chat_process.request.entry");
|
|
1043
|
+
const processResult = await measureHubStage(normalized.id, "chat_entry.req_process.tool_governance", () => runReqProcessStage1ToolGovernance({
|
|
893
1044
|
request: standardizedRequest,
|
|
894
1045
|
rawPayload,
|
|
895
1046
|
metadata: metaBase,
|
|
896
1047
|
entryEndpoint: normalized.entryEndpoint,
|
|
897
1048
|
requestId: normalized.id,
|
|
898
|
-
|
|
899
|
-
|
|
1049
|
+
applyPatchToolMode: normalized.applyPatchToolMode,
|
|
1050
|
+
stageRecorder,
|
|
1051
|
+
}));
|
|
900
1052
|
processedRequest = processResult.processedRequest;
|
|
901
1053
|
// Surface request-side clock reservation into pipeline metadata so response conversion
|
|
902
1054
|
// can commit delivery only after a successful response is produced.
|
|
903
1055
|
try {
|
|
904
|
-
const reservation = processedRequest?.metadata
|
|
905
|
-
|
|
906
|
-
|
|
1056
|
+
const reservation = processedRequest?.metadata
|
|
1057
|
+
?.__clockReservation;
|
|
1058
|
+
if (reservation && typeof reservation === "object") {
|
|
1059
|
+
metaBase.__clockReservation =
|
|
1060
|
+
reservation;
|
|
907
1061
|
}
|
|
908
1062
|
}
|
|
909
1063
|
catch {
|
|
910
1064
|
// best-effort
|
|
911
1065
|
}
|
|
912
1066
|
if (processResult.nodeResult) {
|
|
913
|
-
nodeResults.push(this.convertProcessNodeResult(
|
|
1067
|
+
nodeResults.push(this.convertProcessNodeResult("chat_process.req.stage4.tool_governance", processResult.nodeResult));
|
|
914
1068
|
}
|
|
915
1069
|
}
|
|
916
1070
|
else {
|
|
917
1071
|
nodeResults.push({
|
|
918
|
-
id:
|
|
1072
|
+
id: "chat_process.req.stage4.tool_governance",
|
|
919
1073
|
success: true,
|
|
920
1074
|
metadata: {
|
|
921
|
-
node:
|
|
1075
|
+
node: "chat_process.req.stage4.tool_governance",
|
|
922
1076
|
skipped: true,
|
|
923
|
-
reason:
|
|
924
|
-
}
|
|
1077
|
+
reason: "process_mode_passthrough_parse_record_only",
|
|
1078
|
+
},
|
|
925
1079
|
});
|
|
926
1080
|
if (passthroughAudit) {
|
|
927
1081
|
annotatePassthroughGovernanceSkip(passthroughAudit);
|
|
928
1082
|
}
|
|
929
1083
|
}
|
|
930
|
-
let workingRequest = processedRequest ?? standardizedRequest;
|
|
1084
|
+
let workingRequest = syncResponsesContextFromCanonicalMessages(processedRequest ?? standardizedRequest);
|
|
1085
|
+
const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
|
|
1086
|
+
if (sessionIdentifiers.sessionId &&
|
|
1087
|
+
normalized.metadata &&
|
|
1088
|
+
typeof normalized.metadata === "object") {
|
|
1089
|
+
normalized.metadata.sessionId =
|
|
1090
|
+
sessionIdentifiers.sessionId;
|
|
1091
|
+
}
|
|
1092
|
+
if (sessionIdentifiers.conversationId &&
|
|
1093
|
+
normalized.metadata &&
|
|
1094
|
+
typeof normalized.metadata === "object") {
|
|
1095
|
+
normalized.metadata.conversationId =
|
|
1096
|
+
sessionIdentifiers.conversationId;
|
|
1097
|
+
}
|
|
931
1098
|
// Token estimate for stats/diagnostics (best-effort).
|
|
932
1099
|
try {
|
|
933
|
-
const
|
|
934
|
-
|
|
1100
|
+
const sessionEstimate = await measureHubStage(normalized.id, "chat_entry.req_process.token_estimate.session_delta", () => estimateChatProcessSessionInputTokensDetailed({
|
|
1101
|
+
sessionId: sessionIdentifiers.sessionId,
|
|
1102
|
+
conversationId: sessionIdentifiers.conversationId,
|
|
1103
|
+
}, workingRequest), {
|
|
1104
|
+
mapCompletedDetails: (value) => ({
|
|
1105
|
+
forceLog: value.mode === "unavailable" ||
|
|
1106
|
+
value.mode === "session_reuse" ||
|
|
1107
|
+
value.mode === "session_delta",
|
|
1108
|
+
scope: value.scope,
|
|
1109
|
+
mode: value.mode,
|
|
1110
|
+
reason: value.reason,
|
|
1111
|
+
previousMessageCount: value.previousMessageCount,
|
|
1112
|
+
appendedMessageCount: value.appendedMessageCount,
|
|
1113
|
+
hasPreviousTokens: value.hasPreviousTokens,
|
|
1114
|
+
hasPreviousMessageCount: value.hasPreviousMessageCount,
|
|
1115
|
+
hasToolsSignature: value.hasToolsSignature,
|
|
1116
|
+
hasParametersSignature: value.hasParametersSignature,
|
|
1117
|
+
previousParametersSignatureDigest: value.previousParametersSignatureDigest,
|
|
1118
|
+
currentParametersSignatureDigest: value.currentParametersSignatureDigest,
|
|
1119
|
+
}),
|
|
1120
|
+
});
|
|
1121
|
+
const estimatedTokens = typeof sessionEstimate.tokens === "number" &&
|
|
1122
|
+
Number.isFinite(sessionEstimate.tokens) &&
|
|
1123
|
+
sessionEstimate.tokens > 0
|
|
1124
|
+
? sessionEstimate.tokens
|
|
1125
|
+
: await measureHubStage(normalized.id, "chat_entry.req_process.token_estimate.full_count", () => computeRequestTokens(workingRequest, ""));
|
|
1126
|
+
if (typeof estimatedTokens === "number" &&
|
|
1127
|
+
Number.isFinite(estimatedTokens) &&
|
|
1128
|
+
estimatedTokens > 0) {
|
|
935
1129
|
normalized.metadata = normalized.metadata || {};
|
|
936
|
-
normalized.metadata.estimatedInputTokens =
|
|
1130
|
+
normalized.metadata.estimatedInputTokens =
|
|
1131
|
+
estimatedTokens;
|
|
1132
|
+
saveChatProcessSessionInputEstimate({
|
|
1133
|
+
sessionId: sessionIdentifiers.sessionId,
|
|
1134
|
+
conversationId: sessionIdentifiers.conversationId,
|
|
1135
|
+
}, workingRequest, estimatedTokens);
|
|
937
1136
|
}
|
|
938
1137
|
}
|
|
939
1138
|
catch {
|
|
@@ -942,29 +1141,11 @@ export class HubPipeline {
|
|
|
942
1141
|
const normalizedMeta = normalized.metadata;
|
|
943
1142
|
// responsesResume is a client-protocol semantic (/v1/responses tool loop) and must live in chat.semantics.
|
|
944
1143
|
// Do not read it from metadata once entering chat_process.
|
|
945
|
-
const responsesResume = (
|
|
946
|
-
try {
|
|
947
|
-
const semantics = workingRequest?.semantics;
|
|
948
|
-
const node = semantics && typeof semantics === 'object' && !Array.isArray(semantics) ? semantics.responses : undefined;
|
|
949
|
-
const resume = node && typeof node === 'object' && !Array.isArray(node) ? node.resume : undefined;
|
|
950
|
-
return resume && typeof resume === 'object' && !Array.isArray(resume) ? resume : undefined;
|
|
951
|
-
}
|
|
952
|
-
catch {
|
|
953
|
-
return undefined;
|
|
954
|
-
}
|
|
955
|
-
})();
|
|
1144
|
+
const responsesResume = readResponsesResumeFromRequestSemantics(workingRequest);
|
|
956
1145
|
const stdMetadata = workingRequest?.metadata;
|
|
957
|
-
const hasImageAttachment = (
|
|
958
|
-
(normalizedMeta?.hasImageAttachment === true || normalizedMeta?.hasImageAttachment === 'true');
|
|
1146
|
+
const hasImageAttachment = containsImageAttachment((workingRequest.messages ?? []));
|
|
959
1147
|
const serverToolRequired = stdMetadata?.webSearchEnabled === true ||
|
|
960
1148
|
stdMetadata?.serverToolRequired === true;
|
|
961
|
-
const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
|
|
962
|
-
if (sessionIdentifiers.sessionId && normalized.metadata && typeof normalized.metadata === 'object') {
|
|
963
|
-
normalized.metadata.sessionId = sessionIdentifiers.sessionId;
|
|
964
|
-
}
|
|
965
|
-
if (sessionIdentifiers.conversationId && normalized.metadata && typeof normalized.metadata === 'object') {
|
|
966
|
-
normalized.metadata.conversationId = sessionIdentifiers.conversationId;
|
|
967
|
-
}
|
|
968
1149
|
const disableStickyRoutes = readRuntimeMetadata(normalized.metadata)?.disableStickyRoutes === true;
|
|
969
1150
|
const stopMessageRouterMetadata = resolveStopMessageRouterMetadata(normalized.metadata);
|
|
970
1151
|
const metadataInput = {
|
|
@@ -979,36 +1160,35 @@ export class HubPipeline {
|
|
|
979
1160
|
responsesResume: responsesResume,
|
|
980
1161
|
...(disableStickyRoutes ? { disableStickyRoutes: true } : {}),
|
|
981
1162
|
...(serverToolRequired ? { serverToolRequired: true } : {}),
|
|
982
|
-
...(sessionIdentifiers.sessionId
|
|
983
|
-
|
|
984
|
-
|
|
1163
|
+
...(sessionIdentifiers.sessionId
|
|
1164
|
+
? { sessionId: sessionIdentifiers.sessionId }
|
|
1165
|
+
: {}),
|
|
1166
|
+
...(sessionIdentifiers.conversationId
|
|
1167
|
+
? { conversationId: sessionIdentifiers.conversationId }
|
|
1168
|
+
: {}),
|
|
1169
|
+
...stopMessageRouterMetadata,
|
|
985
1170
|
};
|
|
986
|
-
const routing = runReqProcessStage2RouteSelect({
|
|
1171
|
+
const routing = await measureHubStage(normalized.id, "chat_entry.req_process.route_select", () => runReqProcessStage2RouteSelect({
|
|
987
1172
|
routerEngine: this.routerEngine,
|
|
988
1173
|
request: workingRequest,
|
|
989
1174
|
metadataInput,
|
|
990
1175
|
normalizedMetadata: normalized.metadata,
|
|
991
|
-
stageRecorder
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
if ((stopMessageState || preCommandState) && normalized.metadata && typeof normalized.metadata === 'object') {
|
|
996
|
-
const rt = ensureRuntimeMetadata(normalized.metadata);
|
|
997
|
-
if (stopMessageState) {
|
|
998
|
-
rt.stopMessageState = stopMessageState;
|
|
999
|
-
}
|
|
1000
|
-
if (preCommandState) {
|
|
1001
|
-
rt.preCommandState = preCommandState;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1176
|
+
stageRecorder,
|
|
1177
|
+
}));
|
|
1178
|
+
this.routerEngine.getStopMessageState(metadataInput);
|
|
1179
|
+
this.routerEngine.getPreCommandState(metadataInput);
|
|
1004
1180
|
// Emit virtual router hit log for debugging (same as inbound path).
|
|
1005
1181
|
try {
|
|
1006
1182
|
const routeName = routing.decision?.routeName;
|
|
1007
1183
|
const providerKey = routing.target?.providerKey;
|
|
1008
1184
|
const modelId = workingRequest.model;
|
|
1009
|
-
const logger = (normalized.metadata &&
|
|
1010
|
-
|
|
1011
|
-
|
|
1185
|
+
const logger = (normalized.metadata &&
|
|
1186
|
+
normalized.metadata.logger);
|
|
1187
|
+
if (logger &&
|
|
1188
|
+
typeof logger.logVirtualRouterHit === "function" &&
|
|
1189
|
+
routeName &&
|
|
1190
|
+
providerKey) {
|
|
1191
|
+
logger.logVirtualRouterHit(routeName, providerKey, typeof modelId === "string" ? modelId : undefined);
|
|
1012
1192
|
}
|
|
1013
1193
|
}
|
|
1014
1194
|
catch {
|
|
@@ -1018,20 +1198,22 @@ export class HubPipeline {
|
|
|
1018
1198
|
workingRequest = this.applyOutboundStreamPreference(workingRequest, outboundStream, activeProcessMode);
|
|
1019
1199
|
const outboundAdapterContext = this.buildAdapterContext(normalized, routing.target);
|
|
1020
1200
|
if (routing.target?.compatibilityProfile) {
|
|
1021
|
-
outboundAdapterContext.compatibilityProfile =
|
|
1201
|
+
outboundAdapterContext.compatibilityProfile =
|
|
1202
|
+
routing.target.compatibilityProfile;
|
|
1022
1203
|
}
|
|
1023
1204
|
const outboundProtocol = outboundAdapterContext.providerProtocol;
|
|
1024
|
-
if (activeProcessMode ===
|
|
1205
|
+
if (activeProcessMode === "passthrough" &&
|
|
1206
|
+
outboundProtocol !== normalized.providerProtocol) {
|
|
1025
1207
|
throw new Error(`[HubPipeline] passthrough requires matching protocols: entry=${normalized.providerProtocol}, target=${outboundProtocol}`);
|
|
1026
1208
|
}
|
|
1027
1209
|
const outboundRecorder = this.maybeCreateStageRecorder(outboundAdapterContext, normalized.entryEndpoint, {
|
|
1028
|
-
disableSnapshots: normalized.disableSnapshots === true
|
|
1210
|
+
disableSnapshots: normalized.disableSnapshots === true,
|
|
1029
1211
|
});
|
|
1030
1212
|
const outboundStart = Date.now();
|
|
1031
1213
|
let providerPayload;
|
|
1032
|
-
if (activeProcessMode ===
|
|
1214
|
+
if (activeProcessMode === "passthrough") {
|
|
1033
1215
|
providerPayload = jsonClone(rawPayloadInput);
|
|
1034
|
-
if (typeof outboundStream ===
|
|
1216
|
+
if (typeof outboundStream === "boolean") {
|
|
1035
1217
|
providerPayload.stream = outboundStream;
|
|
1036
1218
|
}
|
|
1037
1219
|
if (passthroughAudit) {
|
|
@@ -1040,69 +1222,75 @@ export class HubPipeline {
|
|
|
1040
1222
|
}
|
|
1041
1223
|
else {
|
|
1042
1224
|
const protocolSwitch = outboundProtocol !== normalized.providerProtocol;
|
|
1043
|
-
const outboundHooks = protocolSwitch
|
|
1225
|
+
const outboundHooks = protocolSwitch
|
|
1226
|
+
? this.resolveProtocolHooks(outboundProtocol)
|
|
1227
|
+
: hooks;
|
|
1044
1228
|
if (!outboundHooks) {
|
|
1045
1229
|
throw new Error(`[HubPipeline] Unsupported provider protocol for hub pipeline: ${outboundProtocol}`);
|
|
1046
1230
|
}
|
|
1047
|
-
const outboundSemanticMapper = protocolSwitch
|
|
1048
|
-
|
|
1231
|
+
const outboundSemanticMapper = protocolSwitch
|
|
1232
|
+
? outboundHooks.createSemanticMapper()
|
|
1233
|
+
: hooks.createSemanticMapper();
|
|
1234
|
+
const outboundContextMetadataKey = protocolSwitch
|
|
1235
|
+
? outboundHooks.contextMetadataKey
|
|
1236
|
+
: hooks.contextMetadataKey;
|
|
1049
1237
|
const outboundContextSnapshot = undefined;
|
|
1050
|
-
const outboundStage1 = await runReqOutboundStage1SemanticMap({
|
|
1238
|
+
const outboundStage1 = await measureHubStage(normalized.id, "chat_entry.req_outbound.semantic_map", () => runReqOutboundStage1SemanticMap({
|
|
1051
1239
|
request: workingRequest,
|
|
1052
1240
|
adapterContext: outboundAdapterContext,
|
|
1053
1241
|
semanticMapper: outboundSemanticMapper,
|
|
1054
1242
|
contextSnapshot: outboundContextSnapshot,
|
|
1055
1243
|
contextMetadataKey: outboundContextMetadataKey,
|
|
1056
|
-
stageRecorder: outboundRecorder
|
|
1057
|
-
});
|
|
1058
|
-
let formattedPayload = await runReqOutboundStage2FormatBuild({
|
|
1244
|
+
stageRecorder: outboundRecorder,
|
|
1245
|
+
}));
|
|
1246
|
+
let formattedPayload = await measureHubStage(normalized.id, "chat_entry.req_outbound.format_build", () => runReqOutboundStage2FormatBuild({
|
|
1059
1247
|
formatEnvelope: outboundStage1.formatEnvelope,
|
|
1060
|
-
stageRecorder: outboundRecorder
|
|
1061
|
-
});
|
|
1062
|
-
formattedPayload = await runReqOutboundStage3Compat({
|
|
1248
|
+
stageRecorder: outboundRecorder,
|
|
1249
|
+
}));
|
|
1250
|
+
formattedPayload = await measureHubStage(normalized.id, "chat_entry.req_outbound.compat", () => runReqOutboundStage3Compat({
|
|
1063
1251
|
payload: formattedPayload,
|
|
1064
1252
|
adapterContext: outboundAdapterContext,
|
|
1065
|
-
stageRecorder: outboundRecorder
|
|
1066
|
-
});
|
|
1253
|
+
stageRecorder: outboundRecorder,
|
|
1254
|
+
}));
|
|
1067
1255
|
// Phase 0/1: observe + enforce provider outbound policy and tool surface (same as inbound path).
|
|
1068
1256
|
const effectivePolicy = normalized.policyOverride ?? this.config.policy;
|
|
1069
1257
|
recordHubPolicyObservation({
|
|
1070
1258
|
policy: effectivePolicy,
|
|
1071
1259
|
providerProtocol: outboundProtocol,
|
|
1072
|
-
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile ===
|
|
1260
|
+
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
1073
1261
|
? outboundAdapterContext.compatibilityProfile
|
|
1074
1262
|
: undefined,
|
|
1075
1263
|
payload: formattedPayload,
|
|
1076
1264
|
stageRecorder: outboundRecorder,
|
|
1077
|
-
requestId: normalized.id
|
|
1265
|
+
requestId: normalized.id,
|
|
1078
1266
|
});
|
|
1079
|
-
providerPayload = applyHubProviderOutboundPolicy({
|
|
1267
|
+
providerPayload = (await measureHubStage(normalized.id, "chat_entry.req_outbound.provider_policy", () => applyHubProviderOutboundPolicy({
|
|
1080
1268
|
policy: effectivePolicy,
|
|
1081
1269
|
providerProtocol: outboundProtocol,
|
|
1082
|
-
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile ===
|
|
1270
|
+
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
1083
1271
|
? outboundAdapterContext.compatibilityProfile
|
|
1084
1272
|
: undefined,
|
|
1085
1273
|
payload: formattedPayload,
|
|
1086
1274
|
stageRecorder: outboundRecorder,
|
|
1087
|
-
requestId: normalized.id
|
|
1088
|
-
});
|
|
1089
|
-
providerPayload = applyProviderOutboundToolSurface({
|
|
1275
|
+
requestId: normalized.id,
|
|
1276
|
+
})));
|
|
1277
|
+
providerPayload = (await measureHubStage(normalized.id, "chat_entry.req_outbound.tool_surface", () => applyProviderOutboundToolSurface({
|
|
1090
1278
|
config: this.config.toolSurface,
|
|
1091
1279
|
providerProtocol: outboundProtocol,
|
|
1092
1280
|
payload: providerPayload,
|
|
1093
1281
|
stageRecorder: outboundRecorder,
|
|
1094
|
-
requestId: normalized.id
|
|
1095
|
-
});
|
|
1282
|
+
requestId: normalized.id,
|
|
1283
|
+
})));
|
|
1096
1284
|
providerPayload = maybeApplyDirectBuiltinWebSearchTool(providerPayload, outboundAdapterContext, outboundProtocol);
|
|
1097
1285
|
recordHubPolicyObservation({
|
|
1098
1286
|
policy: effectivePolicy,
|
|
1099
1287
|
providerProtocol: outboundProtocol,
|
|
1100
|
-
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile ===
|
|
1288
|
+
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
1101
1289
|
? outboundAdapterContext.compatibilityProfile
|
|
1102
1290
|
: undefined,
|
|
1103
1291
|
payload: providerPayload,
|
|
1104
1292
|
stageRecorder: outboundRecorder,
|
|
1105
|
-
requestId: normalized.id
|
|
1293
|
+
requestId: normalized.id,
|
|
1106
1294
|
});
|
|
1107
1295
|
if (passthroughAudit) {
|
|
1108
1296
|
attachPassthroughProviderInputAudit(passthroughAudit, providerPayload, outboundProtocol);
|
|
@@ -1110,28 +1298,31 @@ export class HubPipeline {
|
|
|
1110
1298
|
}
|
|
1111
1299
|
const outboundEnd = Date.now();
|
|
1112
1300
|
nodeResults.push({
|
|
1113
|
-
id:
|
|
1301
|
+
id: "req_outbound",
|
|
1114
1302
|
success: true,
|
|
1115
1303
|
metadata: {
|
|
1116
|
-
node:
|
|
1304
|
+
node: "req_outbound",
|
|
1117
1305
|
executionTime: outboundEnd - outboundStart,
|
|
1118
1306
|
startTime: outboundStart,
|
|
1119
1307
|
endTime: outboundEnd,
|
|
1120
1308
|
dataProcessed: {
|
|
1121
1309
|
messages: workingRequest.messages.length,
|
|
1122
|
-
tools: workingRequest.tools?.length ?? 0
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1310
|
+
tools: workingRequest.tools?.length ?? 0,
|
|
1311
|
+
},
|
|
1312
|
+
},
|
|
1125
1313
|
});
|
|
1126
1314
|
const capturedChatRequest = {
|
|
1127
1315
|
model: workingRequest.model,
|
|
1128
1316
|
messages: jsonClone(workingRequest.messages),
|
|
1129
|
-
tools: workingRequest.tools
|
|
1130
|
-
|
|
1317
|
+
tools: workingRequest.tools
|
|
1318
|
+
? jsonClone(workingRequest.tools)
|
|
1319
|
+
: workingRequest.tools,
|
|
1320
|
+
parameters: workingRequest.parameters
|
|
1321
|
+
? jsonClone(workingRequest.parameters)
|
|
1322
|
+
: workingRequest.parameters,
|
|
1131
1323
|
};
|
|
1132
1324
|
const metadata = {
|
|
1133
1325
|
...normalized.metadata,
|
|
1134
|
-
...(hasImageAttachment ? { hasImageAttachment: true } : {}),
|
|
1135
1326
|
capturedChatRequest,
|
|
1136
1327
|
entryEndpoint: normalized.entryEndpoint,
|
|
1137
1328
|
providerProtocol: outboundProtocol,
|
|
@@ -1140,8 +1331,16 @@ export class HubPipeline {
|
|
|
1140
1331
|
...(passthroughAudit ? { passthroughAudit } : {}),
|
|
1141
1332
|
routeHint: normalized.routeHint,
|
|
1142
1333
|
target: routing.target,
|
|
1143
|
-
...(typeof outboundStream ===
|
|
1334
|
+
...(typeof outboundStream === "boolean"
|
|
1335
|
+
? { providerStream: outboundStream }
|
|
1336
|
+
: {}),
|
|
1144
1337
|
};
|
|
1338
|
+
if (hasImageAttachment) {
|
|
1339
|
+
metadata.hasImageAttachment = true;
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
delete metadata.hasImageAttachment;
|
|
1343
|
+
}
|
|
1145
1344
|
return {
|
|
1146
1345
|
requestId: normalized.id,
|
|
1147
1346
|
providerPayload,
|
|
@@ -1151,19 +1350,31 @@ export class HubPipeline {
|
|
|
1151
1350
|
routingDiagnostics: routing.diagnostics,
|
|
1152
1351
|
target: routing.target,
|
|
1153
1352
|
metadata,
|
|
1154
|
-
nodeResults
|
|
1353
|
+
nodeResults,
|
|
1155
1354
|
};
|
|
1156
1355
|
}
|
|
1157
1356
|
async execute(request) {
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1357
|
+
const requestId = request && typeof request === "object" && typeof request.id === "string" && request.id.trim()
|
|
1358
|
+
? request.id.trim()
|
|
1359
|
+
: `req_${Date.now()}`;
|
|
1360
|
+
const normalized = await measureHubStage(requestId, "execute.normalize_request", () => this.normalizeRequest(request), {
|
|
1361
|
+
mapCompletedDetails: (value) => ({
|
|
1362
|
+
providerProtocol: value.providerProtocol,
|
|
1363
|
+
direction: value.direction,
|
|
1364
|
+
stage: value.stage,
|
|
1365
|
+
processMode: value.processMode,
|
|
1366
|
+
hubEntryMode: value.hubEntryMode ?? null
|
|
1367
|
+
})
|
|
1368
|
+
});
|
|
1369
|
+
if (normalized.direction === "request" &&
|
|
1370
|
+
normalized.hubEntryMode === "chat_process") {
|
|
1371
|
+
return await measureHubStage(normalized.id, "execute.chat_process_entry", () => this.executeChatProcessEntryPipeline(normalized));
|
|
1161
1372
|
}
|
|
1162
1373
|
const hooks = this.resolveProtocolHooks(normalized.providerProtocol);
|
|
1163
1374
|
if (!hooks) {
|
|
1164
1375
|
throw new Error(`Unsupported provider protocol for hub pipeline: ${normalized.providerProtocol}`);
|
|
1165
1376
|
}
|
|
1166
|
-
return await this.executeRequestStagePipeline(normalized, hooks);
|
|
1377
|
+
return await measureHubStage(normalized.id, "execute.request_stage_pipeline", () => this.executeRequestStagePipeline(normalized, hooks));
|
|
1167
1378
|
}
|
|
1168
1379
|
captureAnthropicAliasMap(normalized, adapterContext, chatEnvelope) {
|
|
1169
1380
|
if (!this.shouldCaptureAnthropicAlias(normalized.entryEndpoint)) {
|
|
@@ -1175,7 +1386,9 @@ export class HubPipeline {
|
|
|
1175
1386
|
}
|
|
1176
1387
|
// A1: tool name alias map is mappable semantics and must live in chat.semantics (never metadata).
|
|
1177
1388
|
try {
|
|
1178
|
-
if (!chatEnvelope.semantics ||
|
|
1389
|
+
if (!chatEnvelope.semantics ||
|
|
1390
|
+
typeof chatEnvelope.semantics !== "object" ||
|
|
1391
|
+
Array.isArray(chatEnvelope.semantics)) {
|
|
1179
1392
|
chatEnvelope.semantics = {};
|
|
1180
1393
|
}
|
|
1181
1394
|
const semantics = chatEnvelope.semantics;
|
|
@@ -1183,7 +1396,8 @@ export class HubPipeline {
|
|
|
1183
1396
|
semantics.tools = {};
|
|
1184
1397
|
}
|
|
1185
1398
|
const toolsNode = semantics.tools;
|
|
1186
|
-
if (!isJsonObject(toolsNode.toolNameAliasMap) &&
|
|
1399
|
+
if (!isJsonObject(toolsNode.toolNameAliasMap) &&
|
|
1400
|
+
!isJsonObject(toolsNode.toolAliasMap)) {
|
|
1187
1401
|
toolsNode.toolNameAliasMap = jsonClone(aliasMap);
|
|
1188
1402
|
}
|
|
1189
1403
|
}
|
|
@@ -1192,7 +1406,8 @@ export class HubPipeline {
|
|
|
1192
1406
|
}
|
|
1193
1407
|
}
|
|
1194
1408
|
shouldCaptureAnthropicAlias(endpoint) {
|
|
1195
|
-
return typeof endpoint ===
|
|
1409
|
+
return (typeof endpoint === "string" &&
|
|
1410
|
+
endpoint.toLowerCase().includes("/v1/messages"));
|
|
1196
1411
|
}
|
|
1197
1412
|
resolveAliasMapFromSources(adapterContext, chatEnvelope) {
|
|
1198
1413
|
const fromContext = coerceAliasMap(adapterContext.anthropicToolNameMap);
|
|
@@ -1200,11 +1415,15 @@ export class HubPipeline {
|
|
|
1200
1415
|
return fromContext;
|
|
1201
1416
|
}
|
|
1202
1417
|
const metadataNode = chatEnvelope.metadata;
|
|
1203
|
-
const direct = metadataNode
|
|
1418
|
+
const direct = metadataNode
|
|
1419
|
+
? coerceAliasMap(metadataNode.anthropicToolNameMap)
|
|
1420
|
+
: undefined;
|
|
1204
1421
|
if (direct) {
|
|
1205
1422
|
return direct;
|
|
1206
1423
|
}
|
|
1207
|
-
const contextNode = metadataNode &&
|
|
1424
|
+
const contextNode = metadataNode &&
|
|
1425
|
+
metadataNode.context &&
|
|
1426
|
+
typeof metadataNode.context === "object"
|
|
1208
1427
|
? metadataNode.context
|
|
1209
1428
|
: undefined;
|
|
1210
1429
|
const fromContextNode = coerceAliasMap(contextNode?.anthropicToolNameMap);
|
|
@@ -1215,32 +1434,32 @@ export class HubPipeline {
|
|
|
1215
1434
|
}
|
|
1216
1435
|
resolveProtocolHooks(protocol) {
|
|
1217
1436
|
switch (protocol) {
|
|
1218
|
-
case
|
|
1437
|
+
case "openai-chat":
|
|
1219
1438
|
return {
|
|
1220
1439
|
createFormatAdapter: () => new ChatFormatAdapter(),
|
|
1221
1440
|
createSemanticMapper: () => new ChatSemanticMapper(),
|
|
1222
1441
|
captureContext: (options) => runChatContextCapture(options),
|
|
1223
|
-
contextMetadataKey:
|
|
1442
|
+
contextMetadataKey: "chatContext",
|
|
1224
1443
|
};
|
|
1225
|
-
case
|
|
1444
|
+
case "openai-responses":
|
|
1226
1445
|
return {
|
|
1227
1446
|
createFormatAdapter: () => new ResponsesFormatAdapter(),
|
|
1228
1447
|
createSemanticMapper: () => new ResponsesSemanticMapper(),
|
|
1229
1448
|
captureContext: createResponsesContextCapture(captureResponsesContextSnapshot),
|
|
1230
|
-
contextMetadataKey:
|
|
1449
|
+
contextMetadataKey: "responsesContext",
|
|
1231
1450
|
};
|
|
1232
|
-
case
|
|
1451
|
+
case "anthropic-messages":
|
|
1233
1452
|
return {
|
|
1234
1453
|
createFormatAdapter: () => new AnthropicFormatAdapter(),
|
|
1235
1454
|
createSemanticMapper: () => new AnthropicSemanticMapper(),
|
|
1236
1455
|
captureContext: (options) => runChatContextCapture(options),
|
|
1237
|
-
contextMetadataKey:
|
|
1456
|
+
contextMetadataKey: "anthropicContext",
|
|
1238
1457
|
};
|
|
1239
|
-
case
|
|
1458
|
+
case "gemini-chat":
|
|
1240
1459
|
return {
|
|
1241
1460
|
createFormatAdapter: () => new GeminiFormatAdapter(),
|
|
1242
1461
|
createSemanticMapper: () => new GeminiSemanticMapper(),
|
|
1243
|
-
captureContext: createNoopContextCapture(
|
|
1462
|
+
captureContext: createNoopContextCapture("gemini-chat"),
|
|
1244
1463
|
};
|
|
1245
1464
|
default:
|
|
1246
1465
|
return undefined;
|
|
@@ -1248,31 +1467,45 @@ export class HubPipeline {
|
|
|
1248
1467
|
}
|
|
1249
1468
|
buildAdapterContext(normalized, target) {
|
|
1250
1469
|
const metadata = normalized.metadata || {};
|
|
1251
|
-
const providerProtocol = target?.outboundProfile ||
|
|
1470
|
+
const providerProtocol = target?.outboundProfile ||
|
|
1471
|
+
normalized.providerProtocol;
|
|
1252
1472
|
const providerId = (target?.providerKey || metadata.providerKey);
|
|
1253
1473
|
const routeId = metadata.routeName;
|
|
1254
1474
|
const profileId = (target?.providerKey || metadata.pipelineId);
|
|
1255
|
-
const targetCompatProfile = typeof target?.compatibilityProfile ===
|
|
1475
|
+
const targetCompatProfile = typeof target?.compatibilityProfile === "string" &&
|
|
1476
|
+
target.compatibilityProfile.trim()
|
|
1256
1477
|
? target.compatibilityProfile.trim()
|
|
1257
1478
|
: undefined;
|
|
1258
|
-
const metadataCompatProfile = typeof metadata.compatibilityProfile ===
|
|
1479
|
+
const metadataCompatProfile = typeof metadata.compatibilityProfile ===
|
|
1480
|
+
"string"
|
|
1259
1481
|
? String(metadata.compatibilityProfile).trim()
|
|
1260
1482
|
: undefined;
|
|
1261
1483
|
// When routing has already selected a target runtime, compat must be target-scoped only.
|
|
1262
1484
|
// Never inherit stale top-level metadata.compatibilityProfile from a previous hop.
|
|
1263
|
-
const compatibilityProfile = target
|
|
1264
|
-
|
|
1265
|
-
|
|
1485
|
+
const compatibilityProfile = target
|
|
1486
|
+
? targetCompatProfile
|
|
1487
|
+
: metadataCompatProfile;
|
|
1488
|
+
const streamingHint = normalized.stream === true
|
|
1489
|
+
? "force"
|
|
1490
|
+
: normalized.stream === false
|
|
1491
|
+
? "disable"
|
|
1492
|
+
: "auto";
|
|
1493
|
+
const targetToolCallIdStyle = normalizeReqInboundToolCallIdStyleWithNative(target?.responsesConfig
|
|
1494
|
+
?.toolCallIdStyle);
|
|
1495
|
+
const toolCallIdStyle = targetToolCallIdStyle ?? normalized.toolCallIdStyle;
|
|
1266
1496
|
const adapterContext = {
|
|
1267
1497
|
requestId: normalized.id,
|
|
1268
|
-
entryEndpoint: normalized.entryEndpoint ||
|
|
1498
|
+
entryEndpoint: normalized.entryEndpoint || "/v1/chat/completions",
|
|
1269
1499
|
providerProtocol,
|
|
1270
1500
|
providerId,
|
|
1271
1501
|
routeId,
|
|
1272
1502
|
profileId,
|
|
1273
1503
|
streamingHint,
|
|
1274
1504
|
toolCallIdStyle,
|
|
1275
|
-
...(
|
|
1505
|
+
...(normalized.applyPatchToolMode
|
|
1506
|
+
? { applyPatchToolMode: normalized.applyPatchToolMode }
|
|
1507
|
+
: {}),
|
|
1508
|
+
...(compatibilityProfile ? { compatibilityProfile } : {}),
|
|
1276
1509
|
};
|
|
1277
1510
|
const targetDeepseek = isJsonObject(target?.deepseek)
|
|
1278
1511
|
? jsonClone(target.deepseek)
|
|
@@ -1280,98 +1513,118 @@ export class HubPipeline {
|
|
|
1280
1513
|
if (targetDeepseek) {
|
|
1281
1514
|
adapterContext.deepseek = targetDeepseek;
|
|
1282
1515
|
const rtCarrier = isJsonObject(adapterContext.__rt)
|
|
1283
|
-
? {
|
|
1516
|
+
? {
|
|
1517
|
+
...adapterContext.__rt,
|
|
1518
|
+
}
|
|
1284
1519
|
: {};
|
|
1285
1520
|
rtCarrier.deepseek = targetDeepseek;
|
|
1286
|
-
adapterContext.__rt =
|
|
1521
|
+
adapterContext.__rt =
|
|
1522
|
+
rtCarrier;
|
|
1287
1523
|
}
|
|
1288
1524
|
const runtime = metadata.runtime;
|
|
1289
|
-
if (runtime && typeof runtime ===
|
|
1525
|
+
if (runtime && typeof runtime === "object" && !Array.isArray(runtime)) {
|
|
1290
1526
|
adapterContext.runtime = jsonClone(runtime);
|
|
1291
1527
|
}
|
|
1292
|
-
const clientRequestId = typeof metadata.clientRequestId ===
|
|
1528
|
+
const clientRequestId = typeof metadata.clientRequestId === "string"
|
|
1293
1529
|
? metadata.clientRequestId.trim()
|
|
1294
|
-
:
|
|
1530
|
+
: "";
|
|
1295
1531
|
if (clientRequestId) {
|
|
1296
|
-
adapterContext.clientRequestId =
|
|
1532
|
+
adapterContext.clientRequestId =
|
|
1533
|
+
clientRequestId;
|
|
1297
1534
|
}
|
|
1298
|
-
const groupRequestId = typeof metadata.groupRequestId ===
|
|
1535
|
+
const groupRequestId = typeof metadata.groupRequestId === "string"
|
|
1299
1536
|
? metadata.groupRequestId.trim()
|
|
1300
|
-
:
|
|
1537
|
+
: "";
|
|
1301
1538
|
if (groupRequestId) {
|
|
1302
|
-
adapterContext.groupRequestId =
|
|
1539
|
+
adapterContext.groupRequestId =
|
|
1540
|
+
groupRequestId;
|
|
1303
1541
|
}
|
|
1304
|
-
if (typeof metadata.originalModelId ===
|
|
1542
|
+
if (typeof metadata.originalModelId === "string") {
|
|
1305
1543
|
adapterContext.originalModelId = metadata.originalModelId;
|
|
1306
1544
|
}
|
|
1307
|
-
if (typeof metadata.clientModelId ===
|
|
1545
|
+
if (typeof metadata.clientModelId === "string") {
|
|
1308
1546
|
adapterContext.clientModelId = metadata.clientModelId;
|
|
1309
1547
|
}
|
|
1310
|
-
if (typeof metadata.assignedModelId ===
|
|
1311
|
-
adapterContext.modelId =
|
|
1548
|
+
if (typeof metadata.assignedModelId === "string") {
|
|
1549
|
+
adapterContext.modelId =
|
|
1550
|
+
metadata.assignedModelId;
|
|
1312
1551
|
}
|
|
1313
1552
|
const estimatedInputTokens = Number(metadata.estimatedInputTokens ??
|
|
1314
1553
|
metadata.estimated_tokens ??
|
|
1315
1554
|
metadata.estimatedTokens);
|
|
1316
1555
|
if (Number.isFinite(estimatedInputTokens) && estimatedInputTokens > 0) {
|
|
1317
|
-
adapterContext.estimatedInputTokens =
|
|
1556
|
+
adapterContext.estimatedInputTokens =
|
|
1557
|
+
Math.max(1, Math.round(estimatedInputTokens));
|
|
1318
1558
|
}
|
|
1319
1559
|
const rt = cloneRuntimeMetadata(metadata);
|
|
1320
1560
|
if (rt) {
|
|
1321
1561
|
adapterContext.__rt = rt;
|
|
1322
1562
|
}
|
|
1323
1563
|
const capturedChatRequest = metadata.capturedChatRequest &&
|
|
1324
|
-
typeof metadata.capturedChatRequest ===
|
|
1564
|
+
typeof metadata.capturedChatRequest ===
|
|
1565
|
+
"object" &&
|
|
1325
1566
|
!Array.isArray(metadata.capturedChatRequest)
|
|
1326
|
-
? jsonClone(metadata
|
|
1567
|
+
? jsonClone(metadata
|
|
1568
|
+
.capturedChatRequest)
|
|
1327
1569
|
: undefined;
|
|
1328
1570
|
if (capturedChatRequest) {
|
|
1329
|
-
adapterContext.capturedChatRequest =
|
|
1571
|
+
adapterContext.capturedChatRequest =
|
|
1572
|
+
capturedChatRequest;
|
|
1330
1573
|
}
|
|
1331
|
-
const sessionId = typeof metadata.sessionId ===
|
|
1574
|
+
const sessionId = typeof metadata.sessionId === "string"
|
|
1332
1575
|
? metadata.sessionId.trim()
|
|
1333
|
-
:
|
|
1576
|
+
: "";
|
|
1334
1577
|
if (sessionId) {
|
|
1335
1578
|
adapterContext.sessionId = sessionId;
|
|
1336
1579
|
}
|
|
1337
|
-
const conversationId = typeof metadata.conversationId ===
|
|
1580
|
+
const conversationId = typeof metadata.conversationId === "string"
|
|
1338
1581
|
? metadata.conversationId.trim()
|
|
1339
|
-
:
|
|
1582
|
+
: "";
|
|
1340
1583
|
if (conversationId) {
|
|
1341
|
-
adapterContext.conversationId =
|
|
1584
|
+
adapterContext.conversationId =
|
|
1585
|
+
conversationId;
|
|
1342
1586
|
}
|
|
1343
1587
|
propagateAdapterContextMetadataFields(adapterContext, metadata, [
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1588
|
+
"clockDaemonId",
|
|
1589
|
+
"clockClientDaemonId",
|
|
1590
|
+
"clock_daemon_id",
|
|
1591
|
+
"clock_client_daemon_id",
|
|
1592
|
+
"tmuxSessionId",
|
|
1593
|
+
"tmux_session_id",
|
|
1594
|
+
"clientType",
|
|
1595
|
+
"clockClientType",
|
|
1596
|
+
"clientInjectReady",
|
|
1597
|
+
"clientInjectReason",
|
|
1598
|
+
"client_inject_ready",
|
|
1599
|
+
"client_inject_reason",
|
|
1600
|
+
"workdir",
|
|
1601
|
+
"cwd",
|
|
1602
|
+
"workingDirectory",
|
|
1359
1603
|
]);
|
|
1360
|
-
const clientConnectionState = metadata
|
|
1361
|
-
|
|
1604
|
+
const clientConnectionState = metadata
|
|
1605
|
+
.clientConnectionState;
|
|
1606
|
+
if (clientConnectionState &&
|
|
1607
|
+
typeof clientConnectionState === "object" &&
|
|
1608
|
+
!Array.isArray(clientConnectionState)) {
|
|
1362
1609
|
const stateRecord = clientConnectionState;
|
|
1363
|
-
adapterContext.clientConnectionState =
|
|
1364
|
-
|
|
1365
|
-
|
|
1610
|
+
adapterContext.clientConnectionState =
|
|
1611
|
+
clientConnectionState;
|
|
1612
|
+
if (typeof stateRecord.disconnected === "boolean") {
|
|
1613
|
+
adapterContext.clientDisconnected =
|
|
1614
|
+
stateRecord.disconnected;
|
|
1366
1615
|
}
|
|
1367
1616
|
}
|
|
1368
|
-
const clientDisconnectedRaw = metadata
|
|
1617
|
+
const clientDisconnectedRaw = metadata
|
|
1618
|
+
.clientDisconnected;
|
|
1369
1619
|
if (clientDisconnectedRaw === true ||
|
|
1370
|
-
(typeof clientDisconnectedRaw ===
|
|
1620
|
+
(typeof clientDisconnectedRaw === "string" &&
|
|
1621
|
+
clientDisconnectedRaw.trim().toLowerCase() === "true")) {
|
|
1371
1622
|
adapterContext.clientDisconnected = true;
|
|
1372
1623
|
}
|
|
1373
|
-
if (target?.compatibilityProfile &&
|
|
1374
|
-
|
|
1624
|
+
if (target?.compatibilityProfile &&
|
|
1625
|
+
typeof target.compatibilityProfile === "string") {
|
|
1626
|
+
adapterContext.compatibilityProfile =
|
|
1627
|
+
target.compatibilityProfile;
|
|
1375
1628
|
}
|
|
1376
1629
|
return adapterContext;
|
|
1377
1630
|
}
|
|
@@ -1380,10 +1633,12 @@ export class HubPipeline {
|
|
|
1380
1633
|
return;
|
|
1381
1634
|
}
|
|
1382
1635
|
const params = request.parameters || (request.parameters = {});
|
|
1383
|
-
const direct = typeof params.max_tokens ===
|
|
1636
|
+
const direct = typeof params.max_tokens === "number" &&
|
|
1637
|
+
Number.isFinite(params.max_tokens)
|
|
1384
1638
|
? Math.floor(params.max_tokens)
|
|
1385
1639
|
: undefined;
|
|
1386
|
-
const maxOutputRaw = typeof params.max_output_tokens ===
|
|
1640
|
+
const maxOutputRaw = typeof params.max_output_tokens ===
|
|
1641
|
+
"number" &&
|
|
1387
1642
|
Number.isFinite(params.max_output_tokens)
|
|
1388
1643
|
? Math.floor(params.max_output_tokens)
|
|
1389
1644
|
: undefined;
|
|
@@ -1392,14 +1647,15 @@ export class HubPipeline {
|
|
|
1392
1647
|
if (!desired || desired <= 0) {
|
|
1393
1648
|
desired = 8192;
|
|
1394
1649
|
}
|
|
1395
|
-
let providerCap = typeof target.maxOutputTokens ===
|
|
1650
|
+
let providerCap = typeof target.maxOutputTokens === "number" &&
|
|
1651
|
+
Number.isFinite(target.maxOutputTokens)
|
|
1396
1652
|
? Math.floor(target.maxOutputTokens)
|
|
1397
1653
|
: undefined;
|
|
1398
1654
|
if (!providerCap) {
|
|
1399
|
-
const registry = this.routerEngine
|
|
1400
|
-
.providerRegistry;
|
|
1655
|
+
const registry = this.routerEngine.providerRegistry;
|
|
1401
1656
|
const profile = registry?.get?.(target.providerKey);
|
|
1402
|
-
const candidate = typeof profile?.maxOutputTokens ===
|
|
1657
|
+
const candidate = typeof profile?.maxOutputTokens === "number" &&
|
|
1658
|
+
Number.isFinite(profile.maxOutputTokens)
|
|
1403
1659
|
? Math.floor(profile.maxOutputTokens)
|
|
1404
1660
|
: undefined;
|
|
1405
1661
|
if (candidate && candidate > 0) {
|
|
@@ -1423,7 +1679,7 @@ export class HubPipeline {
|
|
|
1423
1679
|
if (!shouldRecordSnapshots()) {
|
|
1424
1680
|
return undefined;
|
|
1425
1681
|
}
|
|
1426
|
-
const effectiveEndpoint = endpoint || context.entryEndpoint ||
|
|
1682
|
+
const effectiveEndpoint = endpoint || context.entryEndpoint || "/v1/chat/completions";
|
|
1427
1683
|
try {
|
|
1428
1684
|
return createSnapshotRecorder(context, effectiveEndpoint);
|
|
1429
1685
|
}
|
|
@@ -1432,60 +1688,91 @@ export class HubPipeline {
|
|
|
1432
1688
|
}
|
|
1433
1689
|
}
|
|
1434
1690
|
asJsonObject(value) {
|
|
1435
|
-
if (!value || typeof value !==
|
|
1436
|
-
throw new Error(
|
|
1691
|
+
if (!value || typeof value !== "object") {
|
|
1692
|
+
throw new Error("Responses pipeline requires JSON object payload");
|
|
1437
1693
|
}
|
|
1438
1694
|
return value;
|
|
1439
1695
|
}
|
|
1440
1696
|
async normalizeRequest(request) {
|
|
1441
|
-
if (!request || typeof request !==
|
|
1442
|
-
throw new Error(
|
|
1697
|
+
if (!request || typeof request !== "object") {
|
|
1698
|
+
throw new Error("HubPipeline requires request payload");
|
|
1443
1699
|
}
|
|
1444
1700
|
const id = request.id || `req_${Date.now()}`;
|
|
1445
1701
|
const endpoint = normalizeEndpoint(request.endpoint);
|
|
1446
1702
|
const metadataRecord = {
|
|
1447
|
-
...(request.metadata ?? {})
|
|
1703
|
+
...(request.metadata ?? {}),
|
|
1448
1704
|
};
|
|
1449
1705
|
const policyOverride = extractHubPolicyOverride(metadataRecord);
|
|
1450
|
-
if (Object.prototype.hasOwnProperty.call(metadataRecord,
|
|
1706
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "__hubPolicyOverride")) {
|
|
1451
1707
|
delete metadataRecord.__hubPolicyOverride;
|
|
1452
1708
|
}
|
|
1453
1709
|
const shadowCompare = extractHubShadowCompareConfig(metadataRecord);
|
|
1454
|
-
if (Object.prototype.hasOwnProperty.call(metadataRecord,
|
|
1710
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "__hubShadowCompare")) {
|
|
1455
1711
|
delete metadataRecord.__hubShadowCompare;
|
|
1456
1712
|
}
|
|
1457
1713
|
const disableSnapshots = metadataRecord.__disableHubSnapshots === true;
|
|
1458
|
-
if (Object.prototype.hasOwnProperty.call(metadataRecord,
|
|
1714
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "__disableHubSnapshots")) {
|
|
1459
1715
|
delete metadataRecord.__disableHubSnapshots;
|
|
1460
1716
|
}
|
|
1461
|
-
const hubEntryRaw = typeof metadataRecord.__hubEntry ===
|
|
1462
|
-
? String(metadataRecord.__hubEntry)
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1717
|
+
const hubEntryRaw = typeof metadataRecord.__hubEntry === "string"
|
|
1718
|
+
? String(metadataRecord.__hubEntry)
|
|
1719
|
+
.trim()
|
|
1720
|
+
.toLowerCase()
|
|
1721
|
+
: "";
|
|
1722
|
+
const hubEntryMode = hubEntryRaw === "chat_process" ||
|
|
1723
|
+
hubEntryRaw === "chat-process" ||
|
|
1724
|
+
hubEntryRaw === "chatprocess"
|
|
1725
|
+
? "chat_process"
|
|
1466
1726
|
: undefined;
|
|
1467
|
-
if (Object.prototype.hasOwnProperty.call(metadataRecord,
|
|
1727
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "__hubEntry")) {
|
|
1468
1728
|
delete metadataRecord.__hubEntry;
|
|
1469
1729
|
}
|
|
1470
|
-
const entryEndpoint = typeof metadataRecord.entryEndpoint ===
|
|
1730
|
+
const entryEndpoint = typeof metadataRecord.entryEndpoint === "string"
|
|
1471
1731
|
? normalizeEndpoint(metadataRecord.entryEndpoint)
|
|
1472
1732
|
: endpoint;
|
|
1473
1733
|
const providerProtocol = resolveProviderProtocol(metadataRecord.providerProtocol);
|
|
1474
|
-
const processMode = metadataRecord.processMode ===
|
|
1475
|
-
|
|
1476
|
-
|
|
1734
|
+
const processMode = metadataRecord.processMode === "passthrough" ? "passthrough" : "chat";
|
|
1735
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "processMode")) {
|
|
1736
|
+
delete metadataRecord.processMode;
|
|
1737
|
+
}
|
|
1738
|
+
const direction = metadataRecord.direction === "response" ? "response" : "request";
|
|
1739
|
+
const stage = metadataRecord.stage === "outbound" ? "outbound" : "inbound";
|
|
1477
1740
|
const resolvedReadable = this.unwrapReadable(request.payload);
|
|
1478
1741
|
const stream = Boolean(metadataRecord.stream ||
|
|
1479
1742
|
resolvedReadable ||
|
|
1480
|
-
(request.payload &&
|
|
1481
|
-
|
|
1743
|
+
(request.payload &&
|
|
1744
|
+
typeof request.payload === "object" &&
|
|
1745
|
+
request.payload.stream));
|
|
1746
|
+
let payload = await measureHubStage(id, "normalize.materialize_payload", () => this.materializePayload(request.payload, {
|
|
1482
1747
|
requestId: id,
|
|
1483
1748
|
entryEndpoint,
|
|
1484
1749
|
providerProtocol,
|
|
1485
|
-
metadata: metadataRecord
|
|
1486
|
-
}, resolvedReadable)
|
|
1487
|
-
|
|
1488
|
-
|
|
1750
|
+
metadata: metadataRecord,
|
|
1751
|
+
}, resolvedReadable), {
|
|
1752
|
+
startDetails: {
|
|
1753
|
+
hasReadable: Boolean(resolvedReadable),
|
|
1754
|
+
providerProtocol,
|
|
1755
|
+
entryEndpoint
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
const routeHint = typeof metadataRecord.routeHint === "string"
|
|
1759
|
+
? metadataRecord.routeHint
|
|
1760
|
+
: undefined;
|
|
1761
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "routeHint")) {
|
|
1762
|
+
delete metadataRecord.routeHint;
|
|
1763
|
+
}
|
|
1764
|
+
const toolCallIdStyle = normalizeReqInboundToolCallIdStyleWithNative(metadataRecord.toolCallIdStyle);
|
|
1765
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "toolCallIdStyle")) {
|
|
1766
|
+
delete metadataRecord.toolCallIdStyle;
|
|
1767
|
+
}
|
|
1768
|
+
const applyPatchToolMode = resolveApplyPatchToolModeFromEnv() ??
|
|
1769
|
+
resolveApplyPatchToolModeFromTools(Array.isArray(payload.tools)
|
|
1770
|
+
? (payload.tools ?? null)
|
|
1771
|
+
: null);
|
|
1772
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "applyPatchToolMode")) {
|
|
1773
|
+
delete metadataRecord.applyPatchToolMode;
|
|
1774
|
+
}
|
|
1775
|
+
const orchestrationResult = await measureHubStage(id, "normalize.native_orchestration", () => runHubPipelineOrchestrationWithNative({
|
|
1489
1776
|
requestId: id,
|
|
1490
1777
|
endpoint,
|
|
1491
1778
|
entryEndpoint,
|
|
@@ -1498,38 +1785,59 @@ export class HubPipeline {
|
|
|
1498
1785
|
direction,
|
|
1499
1786
|
stage,
|
|
1500
1787
|
stream,
|
|
1501
|
-
...(routeHint ? { routeHint } : {})
|
|
1788
|
+
...(routeHint ? { routeHint } : {}),
|
|
1502
1789
|
},
|
|
1503
1790
|
stream,
|
|
1504
1791
|
processMode,
|
|
1505
1792
|
direction,
|
|
1506
|
-
stage
|
|
1793
|
+
stage,
|
|
1794
|
+
}), {
|
|
1795
|
+
startDetails: {
|
|
1796
|
+
providerProtocol,
|
|
1797
|
+
processMode,
|
|
1798
|
+
direction,
|
|
1799
|
+
stage,
|
|
1800
|
+
stream
|
|
1801
|
+
},
|
|
1802
|
+
mapCompletedDetails: (value) => ({
|
|
1803
|
+
success: value.success,
|
|
1804
|
+
hasPayload: Boolean(value.payload),
|
|
1805
|
+
hasMetadata: Boolean(value.metadata),
|
|
1806
|
+
errorCode: value.error?.code
|
|
1807
|
+
})
|
|
1507
1808
|
});
|
|
1508
1809
|
if (!orchestrationResult.success) {
|
|
1509
|
-
const code = orchestrationResult.error &&
|
|
1810
|
+
const code = orchestrationResult.error &&
|
|
1811
|
+
typeof orchestrationResult.error.code === "string"
|
|
1510
1812
|
? orchestrationResult.error.code.trim()
|
|
1511
|
-
:
|
|
1512
|
-
const message = orchestrationResult.error &&
|
|
1813
|
+
: "hub_pipeline_native_failed";
|
|
1814
|
+
const message = orchestrationResult.error &&
|
|
1815
|
+
typeof orchestrationResult.error.message === "string"
|
|
1513
1816
|
? orchestrationResult.error.message.trim()
|
|
1514
|
-
:
|
|
1817
|
+
: "Native hub pipeline orchestration failed";
|
|
1515
1818
|
throw new Error(`[${code}] ${message}`);
|
|
1516
1819
|
}
|
|
1517
1820
|
if (orchestrationResult.payload) {
|
|
1518
1821
|
payload = orchestrationResult.payload;
|
|
1519
1822
|
}
|
|
1823
|
+
const orchestrationMetadata = {
|
|
1824
|
+
...(orchestrationResult.metadata ?? {}),
|
|
1825
|
+
};
|
|
1826
|
+
if (Object.prototype.hasOwnProperty.call(orchestrationMetadata, "processMode")) {
|
|
1827
|
+
delete orchestrationMetadata.processMode;
|
|
1828
|
+
}
|
|
1829
|
+
if (Object.prototype.hasOwnProperty.call(orchestrationMetadata, "routeHint")) {
|
|
1830
|
+
delete orchestrationMetadata.routeHint;
|
|
1831
|
+
}
|
|
1520
1832
|
const normalizedMetadata = {
|
|
1521
1833
|
...metadataRecord,
|
|
1522
1834
|
entryEndpoint,
|
|
1523
1835
|
providerProtocol,
|
|
1524
|
-
processMode,
|
|
1525
1836
|
direction,
|
|
1526
1837
|
stage,
|
|
1527
1838
|
stream,
|
|
1528
|
-
...
|
|
1839
|
+
...orchestrationMetadata,
|
|
1529
1840
|
};
|
|
1530
|
-
if (routeHint) {
|
|
1531
|
-
normalizedMetadata.routeHint = routeHint;
|
|
1532
|
-
}
|
|
1533
1841
|
return {
|
|
1534
1842
|
id,
|
|
1535
1843
|
endpoint,
|
|
@@ -1545,7 +1853,9 @@ export class HubPipeline {
|
|
|
1545
1853
|
stage,
|
|
1546
1854
|
stream,
|
|
1547
1855
|
routeHint,
|
|
1548
|
-
...(
|
|
1856
|
+
...(toolCallIdStyle ? { toolCallIdStyle } : {}),
|
|
1857
|
+
...(applyPatchToolMode ? { applyPatchToolMode } : {}),
|
|
1858
|
+
...(hubEntryMode ? { hubEntryMode } : {}),
|
|
1549
1859
|
};
|
|
1550
1860
|
}
|
|
1551
1861
|
convertProcessNodeResult(id, result) {
|
|
@@ -1555,11 +1865,11 @@ export class HubPipeline {
|
|
|
1555
1865
|
metadata: result.metadata,
|
|
1556
1866
|
error: result.error
|
|
1557
1867
|
? {
|
|
1558
|
-
code: result.error.code ??
|
|
1868
|
+
code: result.error.code ?? "hub_chat_process_error",
|
|
1559
1869
|
message: result.error.message,
|
|
1560
|
-
details: result.error.details
|
|
1870
|
+
details: result.error.details,
|
|
1561
1871
|
}
|
|
1562
|
-
: undefined
|
|
1872
|
+
: undefined,
|
|
1563
1873
|
};
|
|
1564
1874
|
}
|
|
1565
1875
|
async materializePayload(payload, context, resolvedStream) {
|
|
@@ -1567,8 +1877,8 @@ export class HubPipeline {
|
|
|
1567
1877
|
if (stream) {
|
|
1568
1878
|
return await this.convertSsePayload(stream, context);
|
|
1569
1879
|
}
|
|
1570
|
-
if (!payload || typeof payload !==
|
|
1571
|
-
throw new Error(
|
|
1880
|
+
if (!payload || typeof payload !== "object") {
|
|
1881
|
+
throw new Error("HubPipeline requires JSON object payload");
|
|
1572
1882
|
}
|
|
1573
1883
|
return payload;
|
|
1574
1884
|
}
|
|
@@ -1579,7 +1889,7 @@ export class HubPipeline {
|
|
|
1579
1889
|
if (payload instanceof Readable) {
|
|
1580
1890
|
return payload;
|
|
1581
1891
|
}
|
|
1582
|
-
if (payload && typeof payload ===
|
|
1892
|
+
if (payload && typeof payload === "object" && "readable" in payload) {
|
|
1583
1893
|
const candidate = payload.readable;
|
|
1584
1894
|
if (candidate instanceof Readable) {
|
|
1585
1895
|
return candidate;
|
|
@@ -1594,15 +1904,17 @@ export class HubPipeline {
|
|
|
1594
1904
|
const result = await codec.convertSseToJson(stream, {
|
|
1595
1905
|
requestId: context.requestId,
|
|
1596
1906
|
model: this.extractModelHint(context.metadata),
|
|
1597
|
-
direction:
|
|
1907
|
+
direction: "request",
|
|
1598
1908
|
});
|
|
1599
|
-
if (!result || typeof result !==
|
|
1600
|
-
throw new Error(
|
|
1909
|
+
if (!result || typeof result !== "object") {
|
|
1910
|
+
throw new Error("SSE conversion returned empty payload");
|
|
1601
1911
|
}
|
|
1602
1912
|
return result;
|
|
1603
1913
|
}
|
|
1604
1914
|
catch (error) {
|
|
1605
|
-
const message = error instanceof Error
|
|
1915
|
+
const message = error instanceof Error
|
|
1916
|
+
? error.message
|
|
1917
|
+
: String(error ?? "Unknown error");
|
|
1606
1918
|
throw new Error(`Failed to convert SSE payload for protocol ${protocol}: ${message}`);
|
|
1607
1919
|
}
|
|
1608
1920
|
}
|
|
@@ -1614,17 +1926,17 @@ export class HubPipeline {
|
|
|
1614
1926
|
return context.providerProtocol;
|
|
1615
1927
|
}
|
|
1616
1928
|
extractModelHint(metadata) {
|
|
1617
|
-
if (typeof metadata.model ===
|
|
1929
|
+
if (typeof metadata.model === "string" && metadata.model.trim()) {
|
|
1618
1930
|
return metadata.model;
|
|
1619
1931
|
}
|
|
1620
1932
|
const provider = metadata.provider;
|
|
1621
1933
|
const candidates = [
|
|
1622
1934
|
provider?.model,
|
|
1623
1935
|
provider?.modelId,
|
|
1624
|
-
provider?.defaultModel
|
|
1936
|
+
provider?.defaultModel,
|
|
1625
1937
|
];
|
|
1626
1938
|
for (const candidate of candidates) {
|
|
1627
|
-
if (typeof candidate ===
|
|
1939
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
1628
1940
|
return candidate;
|
|
1629
1941
|
}
|
|
1630
1942
|
}
|
|
@@ -1634,7 +1946,7 @@ export class HubPipeline {
|
|
|
1634
1946
|
return resolveOutboundStreamIntentWithNative(providerPreference);
|
|
1635
1947
|
}
|
|
1636
1948
|
applyOutboundStreamPreference(request, stream, processMode) {
|
|
1637
|
-
if (!request || typeof request !==
|
|
1949
|
+
if (!request || typeof request !== "object") {
|
|
1638
1950
|
return request;
|
|
1639
1951
|
}
|
|
1640
1952
|
return applyOutboundStreamPreferenceWithNative(request, stream, processMode);
|
|
@@ -1646,10 +1958,10 @@ function normalizeEndpoint(endpoint) {
|
|
|
1646
1958
|
function resolveProviderProtocol(value) {
|
|
1647
1959
|
try {
|
|
1648
1960
|
const normalized = resolveHubProviderProtocolWithNative(value);
|
|
1649
|
-
if (normalized ===
|
|
1650
|
-
normalized ===
|
|
1651
|
-
normalized ===
|
|
1652
|
-
normalized ===
|
|
1961
|
+
if (normalized === "openai-chat" ||
|
|
1962
|
+
normalized === "openai-responses" ||
|
|
1963
|
+
normalized === "anthropic-messages" ||
|
|
1964
|
+
normalized === "gemini-chat") {
|
|
1653
1965
|
return normalized;
|
|
1654
1966
|
}
|
|
1655
1967
|
}
|
|
@@ -1669,17 +1981,19 @@ function coerceAliasMap(candidate) {
|
|
|
1669
1981
|
return normalizeAliasMapWithNative(candidate);
|
|
1670
1982
|
}
|
|
1671
1983
|
function readAliasMapFromSemantics(chatEnvelope) {
|
|
1672
|
-
if (!chatEnvelope?.semantics ||
|
|
1984
|
+
if (!chatEnvelope?.semantics ||
|
|
1985
|
+
typeof chatEnvelope.semantics !== "object" ||
|
|
1986
|
+
Array.isArray(chatEnvelope.semantics)) {
|
|
1673
1987
|
return undefined;
|
|
1674
1988
|
}
|
|
1675
1989
|
return resolveAliasMapFromRespSemanticsWithNative(chatEnvelope.semantics);
|
|
1676
1990
|
}
|
|
1677
1991
|
function assertNoMappableSemanticsInMetadata(metadata, scope) {
|
|
1678
|
-
if (!metadata || typeof metadata !==
|
|
1992
|
+
if (!metadata || typeof metadata !== "object") {
|
|
1679
1993
|
return;
|
|
1680
1994
|
}
|
|
1681
1995
|
const present = findMappableSemanticsKeysWithNative(metadata);
|
|
1682
1996
|
if (present.length) {
|
|
1683
|
-
throw new Error(`[HubPipeline][semantic_gate] Mappable semantics must not be stored in metadata (${scope}): ${present.join(
|
|
1997
|
+
throw new Error(`[HubPipeline][semantic_gate] Mappable semantics must not be stored in metadata (${scope}): ${present.join(", ")}`);
|
|
1684
1998
|
}
|
|
1685
1999
|
}
|