@jsonstudio/llms 0.6.3551 → 0.6.3685
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +23 -114
- package/dist/conversion/compat/actions/auto-thinking.js +3 -2
- package/dist/conversion/compat/actions/deepseek-web-response.js +9 -50
- package/dist/conversion/compat/actions/field-mapping.js +2 -153
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +1 -1
- package/dist/conversion/compat/actions/glm-history-image-trim.js +3 -37
- package/dist/conversion/compat/actions/glm-image-content.js +3 -32
- package/dist/conversion/compat/actions/glm-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/glm-native-compat.js +34 -0
- package/dist/conversion/compat/actions/glm-vision-prompt.js +3 -76
- package/dist/conversion/compat/actions/glm-web-search.js +10 -43
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +4 -53
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +5 -141
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +7 -28
- package/dist/conversion/compat/actions/iflow-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/iflow-native-compat.js +36 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +4 -119
- package/dist/conversion/compat/actions/iflow-web-search.js +14 -55
- package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.js +3 -104
- package/dist/conversion/hub/node-support.js +1 -1
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +9 -1
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +5 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +34 -14
- package/dist/conversion/hub/pipeline/hub-pipeline.js +330 -254
- package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +6 -0
- package/dist/conversion/hub/pipeline/hub-stage-timing.js +178 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +6 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +46 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.d.ts +3 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.js +2 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +2 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +1 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.d.ts +3 -2
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.js +18 -5
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.d.ts +1 -2
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.js +0 -16
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +30 -12
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +1 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +5 -2
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +9 -5
- package/dist/conversion/hub/process/chat-process-continue-execution.js +2 -4
- package/dist/conversion/hub/process/chat-process-governance-orchestration.js +3 -1
- package/dist/conversion/hub/process/chat-process-media.d.ts +1 -0
- package/dist/conversion/hub/process/chat-process-media.js +36 -0
- package/dist/conversion/hub/process/chat-process-session-usage.d.ts +25 -0
- package/dist/conversion/hub/process/chat-process-session-usage.js +246 -0
- package/dist/conversion/hub/response/provider-response.js +13 -0
- package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +0 -4
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +0 -12
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -9
- package/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
- package/dist/conversion/responses/responses-openai-bridge.js +51 -24
- package/dist/conversion/shared/anthropic-message-utils.js +14 -1
- package/dist/conversion/shared/reasoning-normalizer.js +61 -0
- package/dist/conversion/shared/tool-governor.js +2 -4
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +6 -0
- package/dist/router/virtual-router/bootstrap.js +1 -6
- package/dist/router/virtual-router/engine/routing-state/store.js +21 -2
- package/dist/router/virtual-router/engine-legacy.js +43 -0
- package/dist/router/virtual-router/engine-logging.d.ts +3 -0
- package/dist/router/virtual-router/engine-logging.js +29 -3
- package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.js +1 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +72 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +1 -1
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +1 -1
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.d.ts +0 -1
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.js +0 -29
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +6 -2
- package/dist/router/virtual-router/engine.js +28 -13
- package/dist/router/virtual-router/provider-registry.js +1 -0
- package/dist/router/virtual-router/routing-instructions/state.js +44 -2
- package/dist/router/virtual-router/routing-instructions/types.d.ts +6 -0
- package/dist/router/virtual-router/token-estimator.js +21 -0
- package/dist/router/virtual-router/types.d.ts +7 -0
- package/dist/servertool/engine.js +3 -34
- package/dist/servertool/handlers/followup-request-builder.js +0 -6
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +3 -274
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +0 -3
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +0 -29
- package/dist/servertool/handlers/stop-message-auto.js +11 -9
- package/dist/servertool/handlers/vision.js +4 -1
- package/dist/servertool/server-side-tools.js +66 -3
- package/dist/tools/apply-patch/execution-capturer.d.ts +1 -1
- package/dist/tools/apply-patch/execution-capturer.js +1 -2
- package/dist/tools/apply-patch/regression-capturer.js +2 -1
- package/dist/tools/tool-registry.js +1 -2
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Readable } from "node:stream";
|
|
2
2
|
import { isJsonObject, jsonClone } from "../types/json.js";
|
|
3
|
+
import { convertMessagesToBridgeInput } from "../../bridge-message-utils.js";
|
|
3
4
|
import { VirtualRouterEngine } from "../../../router/virtual-router/engine.js";
|
|
4
5
|
import { providerErrorCenter } from "../../../router/virtual-router/error-center.js";
|
|
5
6
|
import { providerSuccessCenter } from "../../../router/virtual-router/success-center.js";
|
|
@@ -14,6 +15,7 @@ import { ChatFormatAdapter } from "../format-adapters/chat-format-adapter.js";
|
|
|
14
15
|
import { ChatSemanticMapper } from "../semantic-mappers/chat-mapper.js";
|
|
15
16
|
import { createSnapshotRecorder } from "../snapshot-recorder.js";
|
|
16
17
|
import { shouldRecordSnapshots } from "../../snapshot-utils.js";
|
|
18
|
+
import { measureHubStage } from "./hub-stage-timing.js";
|
|
17
19
|
import { runReqInboundStage1FormatParse } from "./stages/req_inbound/req_inbound_stage1_format_parse/index.js";
|
|
18
20
|
import { runReqInboundStage2SemanticMap } from "./stages/req_inbound/req_inbound_stage2_semantic_map/index.js";
|
|
19
21
|
import { runChatContextCapture, captureResponsesContextSnapshot, } from "./stages/req_inbound/req_inbound_stage3_context_capture/index.js";
|
|
@@ -32,6 +34,8 @@ import { isCompactionRequest } from "../../compaction-detect.js";
|
|
|
32
34
|
import { applyHubProviderOutboundPolicy, recordHubPolicyObservation, setHubPolicyRuntimePolicy, } from "../policy/policy-engine.js";
|
|
33
35
|
import { applyProviderOutboundToolSurface, } from "../tool-surface/tool-surface-engine.js";
|
|
34
36
|
import { cloneRuntimeMetadata, ensureRuntimeMetadata, readRuntimeMetadata, } from "../../runtime-metadata.js";
|
|
37
|
+
import { containsImageAttachment, stripHistoricalImageAttachments, stripHistoricalVisualToolOutputs, } from "../process/chat-process-media.js";
|
|
38
|
+
import { estimateChatProcessSessionInputTokensDetailed, saveChatProcessSessionInputEstimate, } from "../process/chat-process-session-usage.js";
|
|
35
39
|
function isTruthyEnv(value) {
|
|
36
40
|
const v = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
37
41
|
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
@@ -52,6 +56,109 @@ function resolveApplyPatchToolModeFromEnv() {
|
|
|
52
56
|
return "freeform";
|
|
53
57
|
return undefined;
|
|
54
58
|
}
|
|
59
|
+
function applyChatProcessEntryMediaCleanup(request) {
|
|
60
|
+
return {
|
|
61
|
+
...request,
|
|
62
|
+
messages: stripHistoricalVisualToolOutputs(stripHistoricalImageAttachments(request.messages)),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function readResponsesResumeFromMetadata(metadata) {
|
|
66
|
+
if (!metadata || typeof metadata !== "object") {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const resume = metadata.responsesResume;
|
|
70
|
+
return resume && isJsonObject(resume)
|
|
71
|
+
? resume
|
|
72
|
+
: undefined;
|
|
73
|
+
}
|
|
74
|
+
function readResponsesResumeFromRequestSemantics(request) {
|
|
75
|
+
try {
|
|
76
|
+
const semantics = request?.semantics;
|
|
77
|
+
const responses = semantics &&
|
|
78
|
+
typeof semantics === "object" &&
|
|
79
|
+
!Array.isArray(semantics) &&
|
|
80
|
+
semantics.responses &&
|
|
81
|
+
typeof semantics.responses === "object" &&
|
|
82
|
+
!Array.isArray(semantics.responses)
|
|
83
|
+
? semantics.responses
|
|
84
|
+
: undefined;
|
|
85
|
+
const resume = responses &&
|
|
86
|
+
responses.resume &&
|
|
87
|
+
typeof responses.resume === "object" &&
|
|
88
|
+
!Array.isArray(responses.resume)
|
|
89
|
+
? responses.resume
|
|
90
|
+
: undefined;
|
|
91
|
+
return resume;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function liftResponsesResumeIntoSemantics(request, metadata) {
|
|
98
|
+
const resumeMeta = readResponsesResumeFromMetadata(metadata);
|
|
99
|
+
if (!resumeMeta) {
|
|
100
|
+
return request;
|
|
101
|
+
}
|
|
102
|
+
const next = {
|
|
103
|
+
...request,
|
|
104
|
+
semantics: {
|
|
105
|
+
...(request.semantics ??
|
|
106
|
+
{}),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const semantics = next.semantics;
|
|
110
|
+
if (!semantics.responses ||
|
|
111
|
+
typeof semantics.responses !== "object" ||
|
|
112
|
+
Array.isArray(semantics.responses)) {
|
|
113
|
+
semantics.responses = {};
|
|
114
|
+
}
|
|
115
|
+
const responsesNode = semantics.responses;
|
|
116
|
+
if (responsesNode.resume === undefined) {
|
|
117
|
+
responsesNode.resume = jsonClone(resumeMeta);
|
|
118
|
+
}
|
|
119
|
+
delete metadata.responsesResume;
|
|
120
|
+
return next;
|
|
121
|
+
}
|
|
122
|
+
function syncResponsesContextFromCanonicalMessages(request) {
|
|
123
|
+
const semantics = request?.semantics;
|
|
124
|
+
const responsesNode = semantics &&
|
|
125
|
+
typeof semantics === "object" &&
|
|
126
|
+
!Array.isArray(semantics) &&
|
|
127
|
+
semantics.responses &&
|
|
128
|
+
typeof semantics.responses === "object" &&
|
|
129
|
+
!Array.isArray(semantics.responses)
|
|
130
|
+
? semantics.responses
|
|
131
|
+
: undefined;
|
|
132
|
+
const contextNode = responsesNode &&
|
|
133
|
+
responsesNode.context &&
|
|
134
|
+
typeof responsesNode.context === "object" &&
|
|
135
|
+
!Array.isArray(responsesNode.context)
|
|
136
|
+
? responsesNode.context
|
|
137
|
+
: undefined;
|
|
138
|
+
if (!contextNode) {
|
|
139
|
+
return request;
|
|
140
|
+
}
|
|
141
|
+
const bridge = convertMessagesToBridgeInput({
|
|
142
|
+
messages: request.messages ?? [],
|
|
143
|
+
tools: Array.isArray(request.tools)
|
|
144
|
+
? request.tools
|
|
145
|
+
: undefined,
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
...request,
|
|
149
|
+
semantics: {
|
|
150
|
+
...semantics,
|
|
151
|
+
responses: {
|
|
152
|
+
...responsesNode,
|
|
153
|
+
context: {
|
|
154
|
+
...contextNode,
|
|
155
|
+
input: jsonClone(bridge.input),
|
|
156
|
+
originalSystemMessages: jsonClone(bridge.originalSystemMessages),
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
55
162
|
function resolveApplyPatchToolModeFromTools(toolsRaw) {
|
|
56
163
|
return resolveApplyPatchToolModeFromToolsWithNative(toolsRaw);
|
|
57
164
|
}
|
|
@@ -303,23 +410,6 @@ export class HubPipeline {
|
|
|
303
410
|
async executeRequestStagePipeline(normalized, hooks) {
|
|
304
411
|
const semanticMapper = hooks.createSemanticMapper();
|
|
305
412
|
const rawRequest = this.asJsonObject(normalized.payload);
|
|
306
|
-
// Detect applyPatchToolMode (runtime/tooling hint). Client tool schemas are captured as chat semantics
|
|
307
|
-
// in req_inbound_stage2_semantic_map; they must not be stored in metadata.
|
|
308
|
-
try {
|
|
309
|
-
const toolsRaw = Array.isArray(rawRequest?.tools)
|
|
310
|
-
? rawRequest.tools
|
|
311
|
-
: null;
|
|
312
|
-
const applyPatchToolMode = resolveApplyPatchToolModeFromEnv() ??
|
|
313
|
-
resolveApplyPatchToolModeFromTools(toolsRaw);
|
|
314
|
-
if (applyPatchToolMode) {
|
|
315
|
-
normalized.metadata = normalized.metadata || {};
|
|
316
|
-
const rt = ensureRuntimeMetadata(normalized.metadata);
|
|
317
|
-
rt.applyPatchToolMode = applyPatchToolMode;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
// best-effort: do not block request handling due to tool scan failures
|
|
322
|
-
}
|
|
323
413
|
if (isCompactionRequest(rawRequest)) {
|
|
324
414
|
normalized.metadata = normalized.metadata || {};
|
|
325
415
|
const rt = ensureRuntimeMetadata(normalized.metadata);
|
|
@@ -341,16 +431,13 @@ export class HubPipeline {
|
|
|
341
431
|
stageRecorder: inboundRecorder,
|
|
342
432
|
requestId: normalized.id,
|
|
343
433
|
});
|
|
344
|
-
const formatEnvelope = await runReqInboundStage1FormatParse({
|
|
434
|
+
const formatEnvelope = await measureHubStage(normalized.id, "request_stage.req_inbound.format_parse", () => runReqInboundStage1FormatParse({
|
|
345
435
|
rawRequest,
|
|
346
436
|
adapterContext: inboundAdapterContext,
|
|
347
437
|
stageRecorder: inboundRecorder,
|
|
348
|
-
});
|
|
349
|
-
const responsesResumeFromMetadata = normalized.metadata
|
|
350
|
-
|
|
351
|
-
? normalized.metadata.responsesResume
|
|
352
|
-
: undefined;
|
|
353
|
-
const inboundStage2 = await runReqInboundStage2SemanticMap({
|
|
438
|
+
}));
|
|
439
|
+
const responsesResumeFromMetadata = readResponsesResumeFromMetadata(normalized.metadata);
|
|
440
|
+
const inboundStage2 = await measureHubStage(normalized.id, "request_stage.req_inbound.semantic_map", () => runReqInboundStage2SemanticMap({
|
|
354
441
|
adapterContext: inboundAdapterContext,
|
|
355
442
|
formatEnvelope,
|
|
356
443
|
semanticMapper,
|
|
@@ -358,48 +445,23 @@ export class HubPipeline {
|
|
|
358
445
|
? { responsesResume: responsesResumeFromMetadata }
|
|
359
446
|
: {}),
|
|
360
447
|
stageRecorder: inboundRecorder,
|
|
361
|
-
});
|
|
448
|
+
}));
|
|
362
449
|
// responsesResume must not enter chat_process as metadata; it is lifted into chat.semantics in stage2.
|
|
363
450
|
if (responsesResumeFromMetadata &&
|
|
364
451
|
normalized.metadata &&
|
|
365
452
|
Object.prototype.hasOwnProperty.call(normalized.metadata, "responsesResume")) {
|
|
366
453
|
delete normalized.metadata.responsesResume;
|
|
367
454
|
}
|
|
368
|
-
const contextSnapshot = await hooks.captureContext({
|
|
455
|
+
const contextSnapshot = await measureHubStage(normalized.id, "request_stage.req_inbound.context_capture", () => hooks.captureContext({
|
|
369
456
|
rawRequest,
|
|
370
457
|
adapterContext: inboundAdapterContext,
|
|
458
|
+
chatEnvelope: inboundStage2.chatEnvelope,
|
|
371
459
|
stageRecorder: inboundRecorder,
|
|
372
|
-
});
|
|
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
|
-
}
|
|
385
|
-
try {
|
|
386
|
-
const rt = readRuntimeMetadata(normalized.metadata);
|
|
387
|
-
const mode = String(rt?.applyPatchToolMode || "")
|
|
388
|
-
.trim()
|
|
389
|
-
.toLowerCase();
|
|
390
|
-
if (mode === "freeform" || mode === "schema") {
|
|
391
|
-
standardizedRequest.metadata.applyPatchToolMode = mode;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
catch {
|
|
395
|
-
// best-effort: do not block request handling due to metadata propagation failures
|
|
396
|
-
}
|
|
460
|
+
}));
|
|
461
|
+
let standardizedRequest = applyChatProcessEntryMediaCleanup(inboundStage2.standardizedRequest);
|
|
397
462
|
const activeProcessMode = resolveActiveProcessMode(normalized.processMode, standardizedRequest.messages);
|
|
398
463
|
if (activeProcessMode !== normalized.processMode) {
|
|
399
464
|
normalized.processMode = activeProcessMode;
|
|
400
|
-
normalized.metadata = normalized.metadata || {};
|
|
401
|
-
normalized.metadata.processMode =
|
|
402
|
-
activeProcessMode;
|
|
403
465
|
}
|
|
404
466
|
const passthroughAudit = activeProcessMode === "passthrough"
|
|
405
467
|
? buildPassthroughAudit(rawRequest, normalized.providerProtocol)
|
|
@@ -442,14 +504,15 @@ export class HubPipeline {
|
|
|
442
504
|
let processedRequest;
|
|
443
505
|
if (activeProcessMode !== "passthrough") {
|
|
444
506
|
assertNoMappableSemanticsInMetadata(metaBase, "chat_process.request.entry");
|
|
445
|
-
const processResult = await runReqProcessStage1ToolGovernance({
|
|
507
|
+
const processResult = await measureHubStage(normalized.id, "request_stage.req_process.tool_governance", () => runReqProcessStage1ToolGovernance({
|
|
446
508
|
request: standardizedRequest,
|
|
447
509
|
rawPayload: rawRequest,
|
|
448
510
|
metadata: metaBase,
|
|
449
511
|
entryEndpoint: normalized.entryEndpoint,
|
|
450
512
|
requestId: normalized.id,
|
|
513
|
+
applyPatchToolMode: normalized.applyPatchToolMode,
|
|
451
514
|
stageRecorder: inboundRecorder,
|
|
452
|
-
});
|
|
515
|
+
}));
|
|
453
516
|
processedRequest = processResult.processedRequest;
|
|
454
517
|
// Surface request-side clock reservation into pipeline metadata so response conversion
|
|
455
518
|
// can commit delivery only after a successful response is produced.
|
|
@@ -482,17 +545,59 @@ export class HubPipeline {
|
|
|
482
545
|
annotatePassthroughGovernanceSkip(passthroughAudit);
|
|
483
546
|
}
|
|
484
547
|
}
|
|
485
|
-
let workingRequest = processedRequest ?? standardizedRequest;
|
|
548
|
+
let workingRequest = syncResponsesContextFromCanonicalMessages(processedRequest ?? standardizedRequest);
|
|
549
|
+
const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
|
|
550
|
+
if (sessionIdentifiers.sessionId &&
|
|
551
|
+
normalized.metadata &&
|
|
552
|
+
typeof normalized.metadata === "object") {
|
|
553
|
+
normalized.metadata.sessionId =
|
|
554
|
+
sessionIdentifiers.sessionId;
|
|
555
|
+
}
|
|
556
|
+
if (sessionIdentifiers.conversationId &&
|
|
557
|
+
normalized.metadata &&
|
|
558
|
+
typeof normalized.metadata === "object") {
|
|
559
|
+
normalized.metadata.conversationId =
|
|
560
|
+
sessionIdentifiers.conversationId;
|
|
561
|
+
}
|
|
486
562
|
// 使用与 VirtualRouter 一致的 tiktoken 计数逻辑,对标准化请求进行一次
|
|
487
563
|
// 上下文 token 估算,供后续 usage 归一化与统计使用。
|
|
488
564
|
try {
|
|
489
|
-
const
|
|
565
|
+
const sessionEstimate = await measureHubStage(normalized.id, "request_stage.req_process.token_estimate.session_delta", () => estimateChatProcessSessionInputTokensDetailed({
|
|
566
|
+
sessionId: sessionIdentifiers.sessionId,
|
|
567
|
+
conversationId: sessionIdentifiers.conversationId,
|
|
568
|
+
}, workingRequest), {
|
|
569
|
+
mapCompletedDetails: (value) => ({
|
|
570
|
+
forceLog: value.mode === "unavailable" ||
|
|
571
|
+
value.mode === "session_reuse" ||
|
|
572
|
+
value.mode === "session_delta",
|
|
573
|
+
scope: value.scope,
|
|
574
|
+
mode: value.mode,
|
|
575
|
+
reason: value.reason,
|
|
576
|
+
previousMessageCount: value.previousMessageCount,
|
|
577
|
+
appendedMessageCount: value.appendedMessageCount,
|
|
578
|
+
hasPreviousTokens: value.hasPreviousTokens,
|
|
579
|
+
hasPreviousMessageCount: value.hasPreviousMessageCount,
|
|
580
|
+
hasToolsSignature: value.hasToolsSignature,
|
|
581
|
+
hasParametersSignature: value.hasParametersSignature,
|
|
582
|
+
previousParametersSignatureDigest: value.previousParametersSignatureDigest,
|
|
583
|
+
currentParametersSignatureDigest: value.currentParametersSignatureDigest,
|
|
584
|
+
}),
|
|
585
|
+
});
|
|
586
|
+
const estimatedTokens = typeof sessionEstimate.tokens === "number" &&
|
|
587
|
+
Number.isFinite(sessionEstimate.tokens) &&
|
|
588
|
+
sessionEstimate.tokens > 0
|
|
589
|
+
? sessionEstimate.tokens
|
|
590
|
+
: await measureHubStage(normalized.id, "request_stage.req_process.token_estimate.full_count", () => computeRequestTokens(workingRequest, ""));
|
|
490
591
|
if (typeof estimatedTokens === "number" &&
|
|
491
592
|
Number.isFinite(estimatedTokens) &&
|
|
492
593
|
estimatedTokens > 0) {
|
|
493
594
|
normalized.metadata = normalized.metadata || {};
|
|
494
595
|
normalized.metadata.estimatedInputTokens =
|
|
495
596
|
estimatedTokens;
|
|
597
|
+
saveChatProcessSessionInputEstimate({
|
|
598
|
+
sessionId: sessionIdentifiers.sessionId,
|
|
599
|
+
conversationId: sessionIdentifiers.conversationId,
|
|
600
|
+
}, workingRequest, estimatedTokens);
|
|
496
601
|
}
|
|
497
602
|
}
|
|
498
603
|
catch {
|
|
@@ -501,48 +606,11 @@ export class HubPipeline {
|
|
|
501
606
|
const normalizedMeta = normalized.metadata;
|
|
502
607
|
// responsesResume is a client-protocol semantic (/v1/responses tool loop) and must live in chat.semantics.
|
|
503
608
|
// Do not read it from metadata once entering chat_process.
|
|
504
|
-
const responsesResume = (
|
|
505
|
-
try {
|
|
506
|
-
const semantics = workingRequest?.semantics;
|
|
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;
|
|
518
|
-
}
|
|
519
|
-
catch {
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
522
|
-
})();
|
|
609
|
+
const responsesResume = readResponsesResumeFromRequestSemantics(workingRequest);
|
|
523
610
|
const stdMetadata = workingRequest?.metadata;
|
|
524
|
-
const hasImageAttachment =
|
|
525
|
-
stdMetadata?.hasImageAttachment === "true" ||
|
|
526
|
-
normalizedMeta?.hasImageAttachment === true ||
|
|
527
|
-
normalizedMeta?.hasImageAttachment === "true";
|
|
611
|
+
const hasImageAttachment = containsImageAttachment((workingRequest.messages ?? []));
|
|
528
612
|
const serverToolRequired = stdMetadata?.webSearchEnabled === true ||
|
|
529
613
|
stdMetadata?.serverToolRequired === true;
|
|
530
|
-
const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
|
|
531
|
-
// 将从 metadata / clientHeaders 中解析出的会话标识同步回 normalized.metadata,
|
|
532
|
-
// 便于后续 AdapterContext(响应侧 servertool)也能访问到相同的 sessionId /
|
|
533
|
-
// conversationId,用于 sticky-session 相关逻辑(例如 stopMessage)。
|
|
534
|
-
if (sessionIdentifiers.sessionId &&
|
|
535
|
-
normalized.metadata &&
|
|
536
|
-
typeof normalized.metadata === "object") {
|
|
537
|
-
normalized.metadata.sessionId =
|
|
538
|
-
sessionIdentifiers.sessionId;
|
|
539
|
-
}
|
|
540
|
-
if (sessionIdentifiers.conversationId &&
|
|
541
|
-
normalized.metadata &&
|
|
542
|
-
typeof normalized.metadata === "object") {
|
|
543
|
-
normalized.metadata.conversationId =
|
|
544
|
-
sessionIdentifiers.conversationId;
|
|
545
|
-
}
|
|
546
614
|
const disableStickyRoutes = readRuntimeMetadata(normalized.metadata)?.disableStickyRoutes === true;
|
|
547
615
|
const stopMessageRouterMetadata = resolveStopMessageRouterMetadata(normalized.metadata);
|
|
548
616
|
const estimatedInputTokens = (() => {
|
|
@@ -573,28 +641,15 @@ export class HubPipeline {
|
|
|
573
641
|
: {}),
|
|
574
642
|
...stopMessageRouterMetadata,
|
|
575
643
|
};
|
|
576
|
-
const routing = runReqProcessStage2RouteSelect({
|
|
644
|
+
const routing = await measureHubStage(normalized.id, "request_stage.req_process.route_select", () => runReqProcessStage2RouteSelect({
|
|
577
645
|
routerEngine: this.routerEngine,
|
|
578
646
|
request: workingRequest,
|
|
579
647
|
metadataInput,
|
|
580
648
|
normalizedMetadata: normalized.metadata,
|
|
581
649
|
stageRecorder: inboundRecorder,
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if ((stopMessageState || preCommandState) &&
|
|
586
|
-
normalized.metadata &&
|
|
587
|
-
typeof normalized.metadata === "object") {
|
|
588
|
-
const rt = ensureRuntimeMetadata(normalized.metadata);
|
|
589
|
-
if (stopMessageState) {
|
|
590
|
-
rt.stopMessageState =
|
|
591
|
-
stopMessageState;
|
|
592
|
-
}
|
|
593
|
-
if (preCommandState) {
|
|
594
|
-
rt.preCommandState =
|
|
595
|
-
preCommandState;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
650
|
+
}));
|
|
651
|
+
this.routerEngine.getStopMessageState(metadataInput);
|
|
652
|
+
this.routerEngine.getPreCommandState(metadataInput);
|
|
598
653
|
// Emit virtual router hit log for debugging (orange [virtual-router] ...)
|
|
599
654
|
try {
|
|
600
655
|
const routeName = routing.decision?.routeName;
|
|
@@ -660,23 +715,23 @@ export class HubPipeline {
|
|
|
660
715
|
const outboundContextSnapshot = protocolSwitch
|
|
661
716
|
? undefined
|
|
662
717
|
: contextSnapshot;
|
|
663
|
-
const outboundStage1 = await runReqOutboundStage1SemanticMap({
|
|
718
|
+
const outboundStage1 = await measureHubStage(normalized.id, "request_stage.req_outbound.semantic_map", () => runReqOutboundStage1SemanticMap({
|
|
664
719
|
request: workingRequest,
|
|
665
720
|
adapterContext: outboundAdapterContext,
|
|
666
721
|
semanticMapper: outboundSemanticMapper,
|
|
667
722
|
contextSnapshot: outboundContextSnapshot,
|
|
668
723
|
contextMetadataKey: outboundContextMetadataKey,
|
|
669
724
|
stageRecorder: outboundRecorder,
|
|
670
|
-
});
|
|
671
|
-
let formattedPayload = await runReqOutboundStage2FormatBuild({
|
|
725
|
+
}));
|
|
726
|
+
let formattedPayload = await measureHubStage(normalized.id, "request_stage.req_outbound.format_build", () => runReqOutboundStage2FormatBuild({
|
|
672
727
|
formatEnvelope: outboundStage1.formatEnvelope,
|
|
673
728
|
stageRecorder: outboundRecorder,
|
|
674
|
-
});
|
|
675
|
-
formattedPayload = await runReqOutboundStage3Compat({
|
|
729
|
+
}));
|
|
730
|
+
formattedPayload = await measureHubStage(normalized.id, "request_stage.req_outbound.compat", () => runReqOutboundStage3Compat({
|
|
676
731
|
payload: formattedPayload,
|
|
677
732
|
adapterContext: outboundAdapterContext,
|
|
678
733
|
stageRecorder: outboundRecorder,
|
|
679
|
-
});
|
|
734
|
+
}));
|
|
680
735
|
if (shadowCompareBaselineMode) {
|
|
681
736
|
const baselinePolicy = {
|
|
682
737
|
...(effectivePolicy ?? {}),
|
|
@@ -719,7 +774,7 @@ export class HubPipeline {
|
|
|
719
774
|
stageRecorder: outboundRecorder,
|
|
720
775
|
requestId: normalized.id,
|
|
721
776
|
});
|
|
722
|
-
providerPayload = applyHubProviderOutboundPolicy({
|
|
777
|
+
providerPayload = (await measureHubStage(normalized.id, "request_stage.req_outbound.provider_policy", () => applyHubProviderOutboundPolicy({
|
|
723
778
|
policy: effectivePolicy,
|
|
724
779
|
providerProtocol: outboundProtocol,
|
|
725
780
|
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
@@ -728,14 +783,14 @@ export class HubPipeline {
|
|
|
728
783
|
payload: formattedPayload,
|
|
729
784
|
stageRecorder: outboundRecorder,
|
|
730
785
|
requestId: normalized.id,
|
|
731
|
-
});
|
|
732
|
-
providerPayload = applyProviderOutboundToolSurface({
|
|
786
|
+
})));
|
|
787
|
+
providerPayload = (await measureHubStage(normalized.id, "request_stage.req_outbound.tool_surface", () => applyProviderOutboundToolSurface({
|
|
733
788
|
config: this.config.toolSurface,
|
|
734
789
|
providerProtocol: outboundProtocol,
|
|
735
790
|
payload: providerPayload,
|
|
736
791
|
stageRecorder: outboundRecorder,
|
|
737
792
|
requestId: normalized.id,
|
|
738
|
-
});
|
|
793
|
+
})));
|
|
739
794
|
providerPayload = maybeApplyDirectBuiltinWebSearchTool(providerPayload, outboundAdapterContext, outboundProtocol);
|
|
740
795
|
recordHubPolicyObservation({
|
|
741
796
|
policy: effectivePolicy,
|
|
@@ -771,7 +826,7 @@ export class HubPipeline {
|
|
|
771
826
|
//
|
|
772
827
|
// 注意:这里不再根据 processMode(passthrough/chat) 做分支判断——即使某些
|
|
773
828
|
// route 将 processMode 标记为 passthrough,我们仍然需要保留一次规范化后的
|
|
774
|
-
// Chat 请求快照,供 stopMessage
|
|
829
|
+
// Chat 请求快照,供 stopMessage 等被动触发型
|
|
775
830
|
// servertool 在响应阶段使用。
|
|
776
831
|
//
|
|
777
832
|
// 之前这里通过 JSON.stringify/parse 做深拷贝,但在部分 Responses/Gemini
|
|
@@ -796,7 +851,6 @@ export class HubPipeline {
|
|
|
796
851
|
};
|
|
797
852
|
const metadata = {
|
|
798
853
|
...normalized.metadata,
|
|
799
|
-
...(hasImageAttachment ? { hasImageAttachment: true } : {}),
|
|
800
854
|
capturedChatRequest,
|
|
801
855
|
entryEndpoint: normalized.entryEndpoint,
|
|
802
856
|
providerProtocol: outboundProtocol,
|
|
@@ -819,6 +873,12 @@ export class HubPipeline {
|
|
|
819
873
|
}
|
|
820
874
|
: {}),
|
|
821
875
|
};
|
|
876
|
+
if (hasImageAttachment) {
|
|
877
|
+
metadata.hasImageAttachment = true;
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
delete metadata.hasImageAttachment;
|
|
881
|
+
}
|
|
822
882
|
return {
|
|
823
883
|
requestId: normalized.id,
|
|
824
884
|
providerPayload,
|
|
@@ -956,25 +1016,11 @@ export class HubPipeline {
|
|
|
956
1016
|
rtBase.clock = clockConfig;
|
|
957
1017
|
}
|
|
958
1018
|
normalized.metadata = metaBase;
|
|
959
|
-
const
|
|
960
|
-
|
|
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
|
-
}
|
|
1019
|
+
const cleanedRequest = applyChatProcessEntryMediaCleanup(standardizedRequestBase);
|
|
1020
|
+
let standardizedRequest = cleanedRequest;
|
|
972
1021
|
const activeProcessMode = resolveActiveProcessMode(normalized.processMode, cleanedRequest.messages);
|
|
973
1022
|
if (activeProcessMode !== normalized.processMode) {
|
|
974
1023
|
normalized.processMode = activeProcessMode;
|
|
975
|
-
normalized.metadata = normalized.metadata || {};
|
|
976
|
-
normalized.metadata.processMode =
|
|
977
|
-
activeProcessMode;
|
|
978
1024
|
}
|
|
979
1025
|
const passthroughAudit = activeProcessMode === "passthrough"
|
|
980
1026
|
? buildPassthroughAudit(rawPayload, normalized.providerProtocol)
|
|
@@ -982,42 +1028,11 @@ export class HubPipeline {
|
|
|
982
1028
|
// Semantic Gate (chat_process entry): lift any mappable protocol semantics from metadata into request.semantics.
|
|
983
1029
|
// This is the last chance before entering chat_process; after this point we fail-fast on banned metadata keys.
|
|
984
1030
|
try {
|
|
985
|
-
|
|
986
|
-
typeof metaBase.responsesResume === "object" &&
|
|
987
|
-
metaBase.responsesResume
|
|
988
|
-
? metaBase.responsesResume
|
|
989
|
-
: undefined;
|
|
990
|
-
if (resumeMeta) {
|
|
991
|
-
standardizedRequest.semantics =
|
|
992
|
-
standardizedRequest.semantics ?? {};
|
|
993
|
-
const semantics = standardizedRequest.semantics;
|
|
994
|
-
if (!semantics.responses ||
|
|
995
|
-
typeof semantics.responses !== "object" ||
|
|
996
|
-
Array.isArray(semantics.responses)) {
|
|
997
|
-
semantics.responses = {};
|
|
998
|
-
}
|
|
999
|
-
const responsesNode = semantics.responses;
|
|
1000
|
-
if (responsesNode.resume === undefined) {
|
|
1001
|
-
responsesNode.resume = jsonClone(resumeMeta);
|
|
1002
|
-
}
|
|
1003
|
-
delete metaBase.responsesResume;
|
|
1004
|
-
}
|
|
1031
|
+
standardizedRequest = liftResponsesResumeIntoSemantics(standardizedRequest, metaBase);
|
|
1005
1032
|
}
|
|
1006
1033
|
catch {
|
|
1007
1034
|
// best-effort; validation happens below
|
|
1008
1035
|
}
|
|
1009
|
-
try {
|
|
1010
|
-
const rt = readRuntimeMetadata(metaBase);
|
|
1011
|
-
const mode = String(rt?.applyPatchToolMode || "")
|
|
1012
|
-
.trim()
|
|
1013
|
-
.toLowerCase();
|
|
1014
|
-
if (mode === "freeform" || mode === "schema") {
|
|
1015
|
-
standardizedRequest.metadata.applyPatchToolMode = mode;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
catch {
|
|
1019
|
-
// ignore
|
|
1020
|
-
}
|
|
1021
1036
|
const adapterContext = this.buildAdapterContext(normalized);
|
|
1022
1037
|
const stageRecorder = this.maybeCreateStageRecorder(adapterContext, normalized.entryEndpoint, {
|
|
1023
1038
|
disableSnapshots: normalized.disableSnapshots === true,
|
|
@@ -1025,14 +1040,15 @@ export class HubPipeline {
|
|
|
1025
1040
|
let processedRequest;
|
|
1026
1041
|
if (activeProcessMode !== "passthrough") {
|
|
1027
1042
|
assertNoMappableSemanticsInMetadata(metaBase, "chat_process.request.entry");
|
|
1028
|
-
const processResult = await runReqProcessStage1ToolGovernance({
|
|
1043
|
+
const processResult = await measureHubStage(normalized.id, "chat_entry.req_process.tool_governance", () => runReqProcessStage1ToolGovernance({
|
|
1029
1044
|
request: standardizedRequest,
|
|
1030
1045
|
rawPayload,
|
|
1031
1046
|
metadata: metaBase,
|
|
1032
1047
|
entryEndpoint: normalized.entryEndpoint,
|
|
1033
1048
|
requestId: normalized.id,
|
|
1049
|
+
applyPatchToolMode: normalized.applyPatchToolMode,
|
|
1034
1050
|
stageRecorder,
|
|
1035
|
-
});
|
|
1051
|
+
}));
|
|
1036
1052
|
processedRequest = processResult.processedRequest;
|
|
1037
1053
|
// Surface request-side clock reservation into pipeline metadata so response conversion
|
|
1038
1054
|
// can commit delivery only after a successful response is produced.
|
|
@@ -1065,16 +1081,58 @@ export class HubPipeline {
|
|
|
1065
1081
|
annotatePassthroughGovernanceSkip(passthroughAudit);
|
|
1066
1082
|
}
|
|
1067
1083
|
}
|
|
1068
|
-
let workingRequest = processedRequest ?? standardizedRequest;
|
|
1084
|
+
let workingRequest = syncResponsesContextFromCanonicalMessages(processedRequest ?? standardizedRequest);
|
|
1085
|
+
const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
|
|
1086
|
+
if (sessionIdentifiers.sessionId &&
|
|
1087
|
+
normalized.metadata &&
|
|
1088
|
+
typeof normalized.metadata === "object") {
|
|
1089
|
+
normalized.metadata.sessionId =
|
|
1090
|
+
sessionIdentifiers.sessionId;
|
|
1091
|
+
}
|
|
1092
|
+
if (sessionIdentifiers.conversationId &&
|
|
1093
|
+
normalized.metadata &&
|
|
1094
|
+
typeof normalized.metadata === "object") {
|
|
1095
|
+
normalized.metadata.conversationId =
|
|
1096
|
+
sessionIdentifiers.conversationId;
|
|
1097
|
+
}
|
|
1069
1098
|
// Token estimate for stats/diagnostics (best-effort).
|
|
1070
1099
|
try {
|
|
1071
|
-
const
|
|
1100
|
+
const sessionEstimate = await measureHubStage(normalized.id, "chat_entry.req_process.token_estimate.session_delta", () => estimateChatProcessSessionInputTokensDetailed({
|
|
1101
|
+
sessionId: sessionIdentifiers.sessionId,
|
|
1102
|
+
conversationId: sessionIdentifiers.conversationId,
|
|
1103
|
+
}, workingRequest), {
|
|
1104
|
+
mapCompletedDetails: (value) => ({
|
|
1105
|
+
forceLog: value.mode === "unavailable" ||
|
|
1106
|
+
value.mode === "session_reuse" ||
|
|
1107
|
+
value.mode === "session_delta",
|
|
1108
|
+
scope: value.scope,
|
|
1109
|
+
mode: value.mode,
|
|
1110
|
+
reason: value.reason,
|
|
1111
|
+
previousMessageCount: value.previousMessageCount,
|
|
1112
|
+
appendedMessageCount: value.appendedMessageCount,
|
|
1113
|
+
hasPreviousTokens: value.hasPreviousTokens,
|
|
1114
|
+
hasPreviousMessageCount: value.hasPreviousMessageCount,
|
|
1115
|
+
hasToolsSignature: value.hasToolsSignature,
|
|
1116
|
+
hasParametersSignature: value.hasParametersSignature,
|
|
1117
|
+
previousParametersSignatureDigest: value.previousParametersSignatureDigest,
|
|
1118
|
+
currentParametersSignatureDigest: value.currentParametersSignatureDigest,
|
|
1119
|
+
}),
|
|
1120
|
+
});
|
|
1121
|
+
const estimatedTokens = typeof sessionEstimate.tokens === "number" &&
|
|
1122
|
+
Number.isFinite(sessionEstimate.tokens) &&
|
|
1123
|
+
sessionEstimate.tokens > 0
|
|
1124
|
+
? sessionEstimate.tokens
|
|
1125
|
+
: await measureHubStage(normalized.id, "chat_entry.req_process.token_estimate.full_count", () => computeRequestTokens(workingRequest, ""));
|
|
1072
1126
|
if (typeof estimatedTokens === "number" &&
|
|
1073
1127
|
Number.isFinite(estimatedTokens) &&
|
|
1074
1128
|
estimatedTokens > 0) {
|
|
1075
1129
|
normalized.metadata = normalized.metadata || {};
|
|
1076
1130
|
normalized.metadata.estimatedInputTokens =
|
|
1077
1131
|
estimatedTokens;
|
|
1132
|
+
saveChatProcessSessionInputEstimate({
|
|
1133
|
+
sessionId: sessionIdentifiers.sessionId,
|
|
1134
|
+
conversationId: sessionIdentifiers.conversationId,
|
|
1135
|
+
}, workingRequest, estimatedTokens);
|
|
1078
1136
|
}
|
|
1079
1137
|
}
|
|
1080
1138
|
catch {
|
|
@@ -1083,45 +1141,11 @@ export class HubPipeline {
|
|
|
1083
1141
|
const normalizedMeta = normalized.metadata;
|
|
1084
1142
|
// responsesResume is a client-protocol semantic (/v1/responses tool loop) and must live in chat.semantics.
|
|
1085
1143
|
// Do not read it from metadata once entering chat_process.
|
|
1086
|
-
const responsesResume = (
|
|
1087
|
-
try {
|
|
1088
|
-
const semantics = workingRequest?.semantics;
|
|
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;
|
|
1100
|
-
}
|
|
1101
|
-
catch {
|
|
1102
|
-
return undefined;
|
|
1103
|
-
}
|
|
1104
|
-
})();
|
|
1144
|
+
const responsesResume = readResponsesResumeFromRequestSemantics(workingRequest);
|
|
1105
1145
|
const stdMetadata = workingRequest?.metadata;
|
|
1106
|
-
const hasImageAttachment =
|
|
1107
|
-
stdMetadata?.hasImageAttachment === "true" ||
|
|
1108
|
-
normalizedMeta?.hasImageAttachment === true ||
|
|
1109
|
-
normalizedMeta?.hasImageAttachment === "true";
|
|
1146
|
+
const hasImageAttachment = containsImageAttachment((workingRequest.messages ?? []));
|
|
1110
1147
|
const serverToolRequired = stdMetadata?.webSearchEnabled === true ||
|
|
1111
1148
|
stdMetadata?.serverToolRequired === true;
|
|
1112
|
-
const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
|
|
1113
|
-
if (sessionIdentifiers.sessionId &&
|
|
1114
|
-
normalized.metadata &&
|
|
1115
|
-
typeof normalized.metadata === "object") {
|
|
1116
|
-
normalized.metadata.sessionId =
|
|
1117
|
-
sessionIdentifiers.sessionId;
|
|
1118
|
-
}
|
|
1119
|
-
if (sessionIdentifiers.conversationId &&
|
|
1120
|
-
normalized.metadata &&
|
|
1121
|
-
typeof normalized.metadata === "object") {
|
|
1122
|
-
normalized.metadata.conversationId =
|
|
1123
|
-
sessionIdentifiers.conversationId;
|
|
1124
|
-
}
|
|
1125
1149
|
const disableStickyRoutes = readRuntimeMetadata(normalized.metadata)?.disableStickyRoutes === true;
|
|
1126
1150
|
const stopMessageRouterMetadata = resolveStopMessageRouterMetadata(normalized.metadata);
|
|
1127
1151
|
const metadataInput = {
|
|
@@ -1144,28 +1168,15 @@ export class HubPipeline {
|
|
|
1144
1168
|
: {}),
|
|
1145
1169
|
...stopMessageRouterMetadata,
|
|
1146
1170
|
};
|
|
1147
|
-
const routing = runReqProcessStage2RouteSelect({
|
|
1171
|
+
const routing = await measureHubStage(normalized.id, "chat_entry.req_process.route_select", () => runReqProcessStage2RouteSelect({
|
|
1148
1172
|
routerEngine: this.routerEngine,
|
|
1149
1173
|
request: workingRequest,
|
|
1150
1174
|
metadataInput,
|
|
1151
1175
|
normalizedMetadata: normalized.metadata,
|
|
1152
1176
|
stageRecorder,
|
|
1153
|
-
});
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
if ((stopMessageState || preCommandState) &&
|
|
1157
|
-
normalized.metadata &&
|
|
1158
|
-
typeof normalized.metadata === "object") {
|
|
1159
|
-
const rt = ensureRuntimeMetadata(normalized.metadata);
|
|
1160
|
-
if (stopMessageState) {
|
|
1161
|
-
rt.stopMessageState =
|
|
1162
|
-
stopMessageState;
|
|
1163
|
-
}
|
|
1164
|
-
if (preCommandState) {
|
|
1165
|
-
rt.preCommandState =
|
|
1166
|
-
preCommandState;
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1177
|
+
}));
|
|
1178
|
+
this.routerEngine.getStopMessageState(metadataInput);
|
|
1179
|
+
this.routerEngine.getPreCommandState(metadataInput);
|
|
1169
1180
|
// Emit virtual router hit log for debugging (same as inbound path).
|
|
1170
1181
|
try {
|
|
1171
1182
|
const routeName = routing.decision?.routeName;
|
|
@@ -1224,23 +1235,23 @@ export class HubPipeline {
|
|
|
1224
1235
|
? outboundHooks.contextMetadataKey
|
|
1225
1236
|
: hooks.contextMetadataKey;
|
|
1226
1237
|
const outboundContextSnapshot = undefined;
|
|
1227
|
-
const outboundStage1 = await runReqOutboundStage1SemanticMap({
|
|
1238
|
+
const outboundStage1 = await measureHubStage(normalized.id, "chat_entry.req_outbound.semantic_map", () => runReqOutboundStage1SemanticMap({
|
|
1228
1239
|
request: workingRequest,
|
|
1229
1240
|
adapterContext: outboundAdapterContext,
|
|
1230
1241
|
semanticMapper: outboundSemanticMapper,
|
|
1231
1242
|
contextSnapshot: outboundContextSnapshot,
|
|
1232
1243
|
contextMetadataKey: outboundContextMetadataKey,
|
|
1233
1244
|
stageRecorder: outboundRecorder,
|
|
1234
|
-
});
|
|
1235
|
-
let formattedPayload = await runReqOutboundStage2FormatBuild({
|
|
1245
|
+
}));
|
|
1246
|
+
let formattedPayload = await measureHubStage(normalized.id, "chat_entry.req_outbound.format_build", () => runReqOutboundStage2FormatBuild({
|
|
1236
1247
|
formatEnvelope: outboundStage1.formatEnvelope,
|
|
1237
1248
|
stageRecorder: outboundRecorder,
|
|
1238
|
-
});
|
|
1239
|
-
formattedPayload = await runReqOutboundStage3Compat({
|
|
1249
|
+
}));
|
|
1250
|
+
formattedPayload = await measureHubStage(normalized.id, "chat_entry.req_outbound.compat", () => runReqOutboundStage3Compat({
|
|
1240
1251
|
payload: formattedPayload,
|
|
1241
1252
|
adapterContext: outboundAdapterContext,
|
|
1242
1253
|
stageRecorder: outboundRecorder,
|
|
1243
|
-
});
|
|
1254
|
+
}));
|
|
1244
1255
|
// Phase 0/1: observe + enforce provider outbound policy and tool surface (same as inbound path).
|
|
1245
1256
|
const effectivePolicy = normalized.policyOverride ?? this.config.policy;
|
|
1246
1257
|
recordHubPolicyObservation({
|
|
@@ -1253,7 +1264,7 @@ export class HubPipeline {
|
|
|
1253
1264
|
stageRecorder: outboundRecorder,
|
|
1254
1265
|
requestId: normalized.id,
|
|
1255
1266
|
});
|
|
1256
|
-
providerPayload = applyHubProviderOutboundPolicy({
|
|
1267
|
+
providerPayload = (await measureHubStage(normalized.id, "chat_entry.req_outbound.provider_policy", () => applyHubProviderOutboundPolicy({
|
|
1257
1268
|
policy: effectivePolicy,
|
|
1258
1269
|
providerProtocol: outboundProtocol,
|
|
1259
1270
|
compatibilityProfile: typeof outboundAdapterContext.compatibilityProfile === "string"
|
|
@@ -1262,14 +1273,14 @@ export class HubPipeline {
|
|
|
1262
1273
|
payload: formattedPayload,
|
|
1263
1274
|
stageRecorder: outboundRecorder,
|
|
1264
1275
|
requestId: normalized.id,
|
|
1265
|
-
});
|
|
1266
|
-
providerPayload = applyProviderOutboundToolSurface({
|
|
1276
|
+
})));
|
|
1277
|
+
providerPayload = (await measureHubStage(normalized.id, "chat_entry.req_outbound.tool_surface", () => applyProviderOutboundToolSurface({
|
|
1267
1278
|
config: this.config.toolSurface,
|
|
1268
1279
|
providerProtocol: outboundProtocol,
|
|
1269
1280
|
payload: providerPayload,
|
|
1270
1281
|
stageRecorder: outboundRecorder,
|
|
1271
1282
|
requestId: normalized.id,
|
|
1272
|
-
});
|
|
1283
|
+
})));
|
|
1273
1284
|
providerPayload = maybeApplyDirectBuiltinWebSearchTool(providerPayload, outboundAdapterContext, outboundProtocol);
|
|
1274
1285
|
recordHubPolicyObservation({
|
|
1275
1286
|
policy: effectivePolicy,
|
|
@@ -1312,7 +1323,6 @@ export class HubPipeline {
|
|
|
1312
1323
|
};
|
|
1313
1324
|
const metadata = {
|
|
1314
1325
|
...normalized.metadata,
|
|
1315
|
-
...(hasImageAttachment ? { hasImageAttachment: true } : {}),
|
|
1316
1326
|
capturedChatRequest,
|
|
1317
1327
|
entryEndpoint: normalized.entryEndpoint,
|
|
1318
1328
|
providerProtocol: outboundProtocol,
|
|
@@ -1325,6 +1335,12 @@ export class HubPipeline {
|
|
|
1325
1335
|
? { providerStream: outboundStream }
|
|
1326
1336
|
: {}),
|
|
1327
1337
|
};
|
|
1338
|
+
if (hasImageAttachment) {
|
|
1339
|
+
metadata.hasImageAttachment = true;
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
delete metadata.hasImageAttachment;
|
|
1343
|
+
}
|
|
1328
1344
|
return {
|
|
1329
1345
|
requestId: normalized.id,
|
|
1330
1346
|
providerPayload,
|
|
@@ -1338,16 +1354,27 @@ export class HubPipeline {
|
|
|
1338
1354
|
};
|
|
1339
1355
|
}
|
|
1340
1356
|
async execute(request) {
|
|
1341
|
-
const
|
|
1357
|
+
const requestId = request && typeof request === "object" && typeof request.id === "string" && request.id.trim()
|
|
1358
|
+
? request.id.trim()
|
|
1359
|
+
: `req_${Date.now()}`;
|
|
1360
|
+
const normalized = await measureHubStage(requestId, "execute.normalize_request", () => this.normalizeRequest(request), {
|
|
1361
|
+
mapCompletedDetails: (value) => ({
|
|
1362
|
+
providerProtocol: value.providerProtocol,
|
|
1363
|
+
direction: value.direction,
|
|
1364
|
+
stage: value.stage,
|
|
1365
|
+
processMode: value.processMode,
|
|
1366
|
+
hubEntryMode: value.hubEntryMode ?? null
|
|
1367
|
+
})
|
|
1368
|
+
});
|
|
1342
1369
|
if (normalized.direction === "request" &&
|
|
1343
1370
|
normalized.hubEntryMode === "chat_process") {
|
|
1344
|
-
return await this.executeChatProcessEntryPipeline(normalized);
|
|
1371
|
+
return await measureHubStage(normalized.id, "execute.chat_process_entry", () => this.executeChatProcessEntryPipeline(normalized));
|
|
1345
1372
|
}
|
|
1346
1373
|
const hooks = this.resolveProtocolHooks(normalized.providerProtocol);
|
|
1347
1374
|
if (!hooks) {
|
|
1348
1375
|
throw new Error(`Unsupported provider protocol for hub pipeline: ${normalized.providerProtocol}`);
|
|
1349
1376
|
}
|
|
1350
|
-
return await this.executeRequestStagePipeline(normalized, hooks);
|
|
1377
|
+
return await measureHubStage(normalized.id, "execute.request_stage_pipeline", () => this.executeRequestStagePipeline(normalized, hooks));
|
|
1351
1378
|
}
|
|
1352
1379
|
captureAnthropicAliasMap(normalized, adapterContext, chatEnvelope) {
|
|
1353
1380
|
if (!this.shouldCaptureAnthropicAlias(normalized.entryEndpoint)) {
|
|
@@ -1463,7 +1490,9 @@ export class HubPipeline {
|
|
|
1463
1490
|
: normalized.stream === false
|
|
1464
1491
|
? "disable"
|
|
1465
1492
|
: "auto";
|
|
1466
|
-
const
|
|
1493
|
+
const targetToolCallIdStyle = normalizeReqInboundToolCallIdStyleWithNative(target?.responsesConfig
|
|
1494
|
+
?.toolCallIdStyle);
|
|
1495
|
+
const toolCallIdStyle = targetToolCallIdStyle ?? normalized.toolCallIdStyle;
|
|
1467
1496
|
const adapterContext = {
|
|
1468
1497
|
requestId: normalized.id,
|
|
1469
1498
|
entryEndpoint: normalized.entryEndpoint || "/v1/chat/completions",
|
|
@@ -1473,6 +1502,9 @@ export class HubPipeline {
|
|
|
1473
1502
|
profileId,
|
|
1474
1503
|
streamingHint,
|
|
1475
1504
|
toolCallIdStyle,
|
|
1505
|
+
...(normalized.applyPatchToolMode
|
|
1506
|
+
? { applyPatchToolMode: normalized.applyPatchToolMode }
|
|
1507
|
+
: {}),
|
|
1476
1508
|
...(compatibilityProfile ? { compatibilityProfile } : {}),
|
|
1477
1509
|
};
|
|
1478
1510
|
const targetDeepseek = isJsonObject(target?.deepseek)
|
|
@@ -1700,6 +1732,9 @@ export class HubPipeline {
|
|
|
1700
1732
|
: endpoint;
|
|
1701
1733
|
const providerProtocol = resolveProviderProtocol(metadataRecord.providerProtocol);
|
|
1702
1734
|
const processMode = metadataRecord.processMode === "passthrough" ? "passthrough" : "chat";
|
|
1735
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "processMode")) {
|
|
1736
|
+
delete metadataRecord.processMode;
|
|
1737
|
+
}
|
|
1703
1738
|
const direction = metadataRecord.direction === "response" ? "response" : "request";
|
|
1704
1739
|
const stage = metadataRecord.stage === "outbound" ? "outbound" : "inbound";
|
|
1705
1740
|
const resolvedReadable = this.unwrapReadable(request.payload);
|
|
@@ -1708,16 +1743,36 @@ export class HubPipeline {
|
|
|
1708
1743
|
(request.payload &&
|
|
1709
1744
|
typeof request.payload === "object" &&
|
|
1710
1745
|
request.payload.stream));
|
|
1711
|
-
let payload = await this.materializePayload(request.payload, {
|
|
1746
|
+
let payload = await measureHubStage(id, "normalize.materialize_payload", () => this.materializePayload(request.payload, {
|
|
1712
1747
|
requestId: id,
|
|
1713
1748
|
entryEndpoint,
|
|
1714
1749
|
providerProtocol,
|
|
1715
1750
|
metadata: metadataRecord,
|
|
1716
|
-
}, resolvedReadable)
|
|
1751
|
+
}, resolvedReadable), {
|
|
1752
|
+
startDetails: {
|
|
1753
|
+
hasReadable: Boolean(resolvedReadable),
|
|
1754
|
+
providerProtocol,
|
|
1755
|
+
entryEndpoint
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1717
1758
|
const routeHint = typeof metadataRecord.routeHint === "string"
|
|
1718
1759
|
? metadataRecord.routeHint
|
|
1719
1760
|
: undefined;
|
|
1720
|
-
|
|
1761
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "routeHint")) {
|
|
1762
|
+
delete metadataRecord.routeHint;
|
|
1763
|
+
}
|
|
1764
|
+
const toolCallIdStyle = normalizeReqInboundToolCallIdStyleWithNative(metadataRecord.toolCallIdStyle);
|
|
1765
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "toolCallIdStyle")) {
|
|
1766
|
+
delete metadataRecord.toolCallIdStyle;
|
|
1767
|
+
}
|
|
1768
|
+
const applyPatchToolMode = resolveApplyPatchToolModeFromEnv() ??
|
|
1769
|
+
resolveApplyPatchToolModeFromTools(Array.isArray(payload.tools)
|
|
1770
|
+
? (payload.tools ?? null)
|
|
1771
|
+
: null);
|
|
1772
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, "applyPatchToolMode")) {
|
|
1773
|
+
delete metadataRecord.applyPatchToolMode;
|
|
1774
|
+
}
|
|
1775
|
+
const orchestrationResult = await measureHubStage(id, "normalize.native_orchestration", () => runHubPipelineOrchestrationWithNative({
|
|
1721
1776
|
requestId: id,
|
|
1722
1777
|
endpoint,
|
|
1723
1778
|
entryEndpoint,
|
|
@@ -1736,6 +1791,20 @@ export class HubPipeline {
|
|
|
1736
1791
|
processMode,
|
|
1737
1792
|
direction,
|
|
1738
1793
|
stage,
|
|
1794
|
+
}), {
|
|
1795
|
+
startDetails: {
|
|
1796
|
+
providerProtocol,
|
|
1797
|
+
processMode,
|
|
1798
|
+
direction,
|
|
1799
|
+
stage,
|
|
1800
|
+
stream
|
|
1801
|
+
},
|
|
1802
|
+
mapCompletedDetails: (value) => ({
|
|
1803
|
+
success: value.success,
|
|
1804
|
+
hasPayload: Boolean(value.payload),
|
|
1805
|
+
hasMetadata: Boolean(value.metadata),
|
|
1806
|
+
errorCode: value.error?.code
|
|
1807
|
+
})
|
|
1739
1808
|
});
|
|
1740
1809
|
if (!orchestrationResult.success) {
|
|
1741
1810
|
const code = orchestrationResult.error &&
|
|
@@ -1751,19 +1820,24 @@ export class HubPipeline {
|
|
|
1751
1820
|
if (orchestrationResult.payload) {
|
|
1752
1821
|
payload = orchestrationResult.payload;
|
|
1753
1822
|
}
|
|
1823
|
+
const orchestrationMetadata = {
|
|
1824
|
+
...(orchestrationResult.metadata ?? {}),
|
|
1825
|
+
};
|
|
1826
|
+
if (Object.prototype.hasOwnProperty.call(orchestrationMetadata, "processMode")) {
|
|
1827
|
+
delete orchestrationMetadata.processMode;
|
|
1828
|
+
}
|
|
1829
|
+
if (Object.prototype.hasOwnProperty.call(orchestrationMetadata, "routeHint")) {
|
|
1830
|
+
delete orchestrationMetadata.routeHint;
|
|
1831
|
+
}
|
|
1754
1832
|
const normalizedMetadata = {
|
|
1755
1833
|
...metadataRecord,
|
|
1756
1834
|
entryEndpoint,
|
|
1757
1835
|
providerProtocol,
|
|
1758
|
-
processMode,
|
|
1759
1836
|
direction,
|
|
1760
1837
|
stage,
|
|
1761
1838
|
stream,
|
|
1762
|
-
...
|
|
1839
|
+
...orchestrationMetadata,
|
|
1763
1840
|
};
|
|
1764
|
-
if (routeHint) {
|
|
1765
|
-
normalizedMetadata.routeHint = routeHint;
|
|
1766
|
-
}
|
|
1767
1841
|
return {
|
|
1768
1842
|
id,
|
|
1769
1843
|
endpoint,
|
|
@@ -1779,6 +1853,8 @@ export class HubPipeline {
|
|
|
1779
1853
|
stage,
|
|
1780
1854
|
stream,
|
|
1781
1855
|
routeHint,
|
|
1856
|
+
...(toolCallIdStyle ? { toolCallIdStyle } : {}),
|
|
1857
|
+
...(applyPatchToolMode ? { applyPatchToolMode } : {}),
|
|
1782
1858
|
...(hubEntryMode ? { hubEntryMode } : {}),
|
|
1783
1859
|
};
|
|
1784
1860
|
}
|