@jsonstudio/llms 0.6.3539 → 0.6.3551

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