@jsonstudio/llms 0.6.3541 → 0.6.3685

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