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