@jsonstudio/llms 0.6.3551 → 0.6.3685

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