@jsonstudio/llms 0.6.3551 → 0.6.3686

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 (66) hide show
  1. package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +4 -115
  2. package/dist/conversion/compat/actions/auto-thinking.js +3 -2
  3. package/dist/conversion/compat/actions/deepseek-web-response.js +15 -49
  4. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
  5. package/dist/conversion/compat/actions/gemini-cli-request.js +1 -1
  6. package/dist/conversion/compat/actions/glm-history-image-trim.js +3 -37
  7. package/dist/conversion/compat/actions/glm-image-content.js +3 -32
  8. package/dist/conversion/compat/actions/glm-native-compat.d.ts +6 -0
  9. package/dist/conversion/compat/actions/glm-native-compat.js +34 -0
  10. package/dist/conversion/compat/actions/glm-vision-prompt.js +3 -76
  11. package/dist/conversion/compat/actions/glm-web-search.js +10 -43
  12. package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +4 -53
  13. package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +5 -141
  14. package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +7 -28
  15. package/dist/conversion/compat/actions/iflow-native-compat.d.ts +6 -0
  16. package/dist/conversion/compat/actions/iflow-native-compat.js +36 -0
  17. package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +4 -119
  18. package/dist/conversion/compat/actions/iflow-web-search.js +14 -55
  19. package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +5 -0
  20. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +31 -18
  21. package/dist/conversion/hub/pipeline/hub-pipeline.js +163 -163
  22. package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +6 -0
  23. package/dist/conversion/hub/pipeline/hub-stage-timing.js +178 -0
  24. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +4 -4
  25. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +33 -14
  26. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -6
  27. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +41 -23
  28. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +44 -1
  29. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -1
  30. package/dist/conversion/hub/process/chat-process-continue-execution.js +5 -4
  31. package/dist/conversion/hub/process/chat-process-governance-orchestration.js +3 -1
  32. package/dist/conversion/hub/process/chat-process-media.d.ts +3 -1
  33. package/dist/conversion/hub/process/chat-process-media.js +92 -2
  34. package/dist/conversion/hub/process/chat-process-session-usage.d.ts +7 -0
  35. package/dist/conversion/hub/process/chat-process-session-usage.js +147 -0
  36. package/dist/conversion/hub/response/provider-response.js +13 -0
  37. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +0 -12
  38. package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -9
  39. package/dist/conversion/responses/responses-openai-bridge.js +77 -44
  40. package/dist/conversion/shared/reasoning-normalizer.js +42 -0
  41. package/dist/conversion/shared/responses-tool-utils.js +2 -3
  42. package/dist/native/router_hotpath_napi.node +0 -0
  43. package/dist/router/virtual-router/bootstrap/profile-builder.js +1 -0
  44. package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +1 -0
  45. package/dist/router/virtual-router/bootstrap/provider-normalization.js +6 -0
  46. package/dist/router/virtual-router/bootstrap.js +1 -6
  47. package/dist/router/virtual-router/engine-legacy.js +43 -0
  48. package/dist/router/virtual-router/engine-logging.d.ts +3 -0
  49. package/dist/router/virtual-router/engine-logging.js +29 -3
  50. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.d.ts +2 -2
  51. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.js +96 -80
  52. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +1 -0
  53. package/dist/router/virtual-router/engine.js +34 -22
  54. package/dist/router/virtual-router/provider-registry.js +1 -0
  55. package/dist/router/virtual-router/routing-instructions/state.js +35 -3
  56. package/dist/router/virtual-router/routing-instructions/types.d.ts +4 -0
  57. package/dist/router/virtual-router/types.d.ts +7 -0
  58. package/dist/servertool/engine.js +3 -34
  59. package/dist/servertool/handlers/followup-request-builder.js +0 -6
  60. package/dist/servertool/handlers/gemini-empty-reply-continue.js +3 -274
  61. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +0 -3
  62. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +0 -29
  63. package/dist/servertool/handlers/stop-message-auto.js +2 -10
  64. package/dist/servertool/handlers/vision.js +4 -1
  65. package/dist/servertool/server-side-tools.js +66 -3
  66. 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";
@@ -26,12 +27,15 @@ import { runReqOutboundStage2FormatBuild } from "./stages/req_outbound/req_outbo
26
27
  import { runReqOutboundStage3Compat } from "./stages/req_outbound/req_outbound_stage3_compat/index.js";
27
28
  import { extractSessionIdentifiersFromMetadata } from "./session-identifiers.js";
28
29
  import { computeRequestTokens } from "../../../router/virtual-router/token-estimator.js";
30
+ import { estimateSessionBoundTokens } from "../process/chat-process-session-usage.js";
29
31
  import { annotatePassthroughGovernanceSkipWithNative, attachPassthroughProviderInputAuditWithNative, buildPassthroughAuditWithNative, applyOutboundStreamPreferenceWithNative, normalizeHubEndpointWithNative, extractAdapterContextMetadataFieldsWithNative, resolveApplyPatchToolModeFromToolsWithNative, resolveHubClientProtocolWithNative, resolveHubPolicyOverrideFromMetadataWithNative, resolveHubProviderProtocolWithNative, resolveOutboundStreamIntentWithNative, resolveHubShadowCompareConfigWithNative, resolveActiveProcessModeWithNative, findMappableSemanticsKeysWithNative, resolveHubSseProtocolFromMetadataWithNative, resolveStopMessageRouterMetadataWithNative, runHubPipelineOrchestrationWithNative, } from "../../../router/virtual-router/engine-selection/native-hub-pipeline-orchestration-semantics.js";
30
32
  import { normalizeAliasMapWithNative, resolveAliasMapFromRespSemanticsWithNative, } from "../../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js";
31
33
  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, repairIncompleteToolCalls, } from "../process/chat-process-media.js";
38
+ import { measureHubStage, logHubStageTiming } from "./hub-stage-timing.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: repairIncompleteToolCalls(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
  }
@@ -341,16 +448,13 @@ export class HubPipeline {
341
448
  stageRecorder: inboundRecorder,
342
449
  requestId: normalized.id,
343
450
  });
344
- const formatEnvelope = await runReqInboundStage1FormatParse({
451
+ const formatEnvelope = await measureHubStage(normalized.id, "req_inbound.stage1_format_parse", () => runReqInboundStage1FormatParse({
345
452
  rawRequest,
346
453
  adapterContext: inboundAdapterContext,
347
454
  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({
455
+ }));
456
+ const responsesResumeFromMetadata = readResponsesResumeFromMetadata(normalized.metadata);
457
+ const inboundStage2 = await measureHubStage(normalized.id, "req_inbound.stage2_semantic_map", () => runReqInboundStage2SemanticMap({
354
458
  adapterContext: inboundAdapterContext,
355
459
  formatEnvelope,
356
460
  semanticMapper,
@@ -358,30 +462,24 @@ export class HubPipeline {
358
462
  ? { responsesResume: responsesResumeFromMetadata }
359
463
  : {}),
360
464
  stageRecorder: inboundRecorder,
361
- });
465
+ }));
362
466
  // responsesResume must not enter chat_process as metadata; it is lifted into chat.semantics in stage2.
363
467
  if (responsesResumeFromMetadata &&
364
468
  normalized.metadata &&
365
469
  Object.prototype.hasOwnProperty.call(normalized.metadata, "responsesResume")) {
366
470
  delete normalized.metadata.responsesResume;
367
471
  }
368
- const contextSnapshot = await hooks.captureContext({
369
- rawRequest,
370
- adapterContext: inboundAdapterContext,
371
- stageRecorder: inboundRecorder,
472
+ const contextSnapshot = await measureHubStage(normalized.id, "req_inbound.stage3_context_capture", () => {
473
+ if (inboundStage2.responsesContext) {
474
+ return inboundStage2.responsesContext;
475
+ }
476
+ return hooks.captureContext({
477
+ rawRequest,
478
+ adapterContext: inboundAdapterContext,
479
+ stageRecorder: inboundRecorder,
480
+ });
372
481
  });
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
- }
482
+ let standardizedRequest = applyChatProcessEntryMediaCleanup(inboundStage2.standardizedRequest);
385
483
  try {
386
484
  const rt = readRuntimeMetadata(normalized.metadata);
387
485
  const mode = String(rt?.applyPatchToolMode || "")
@@ -397,9 +495,6 @@ export class HubPipeline {
397
495
  const activeProcessMode = resolveActiveProcessMode(normalized.processMode, standardizedRequest.messages);
398
496
  if (activeProcessMode !== normalized.processMode) {
399
497
  normalized.processMode = activeProcessMode;
400
- normalized.metadata = normalized.metadata || {};
401
- normalized.metadata.processMode =
402
- activeProcessMode;
403
498
  }
404
499
  const passthroughAudit = activeProcessMode === "passthrough"
405
500
  ? buildPassthroughAudit(rawRequest, normalized.providerProtocol)
@@ -442,14 +537,14 @@ export class HubPipeline {
442
537
  let processedRequest;
443
538
  if (activeProcessMode !== "passthrough") {
444
539
  assertNoMappableSemanticsInMetadata(metaBase, "chat_process.request.entry");
445
- const processResult = await runReqProcessStage1ToolGovernance({
540
+ const processResult = await measureHubStage(normalized.id, "req_process.stage1_tool_governance", () => runReqProcessStage1ToolGovernance({
446
541
  request: standardizedRequest,
447
542
  rawPayload: rawRequest,
448
543
  metadata: metaBase,
449
544
  entryEndpoint: normalized.entryEndpoint,
450
545
  requestId: normalized.id,
451
546
  stageRecorder: inboundRecorder,
452
- });
547
+ }));
453
548
  processedRequest = processResult.processedRequest;
454
549
  // Surface request-side clock reservation into pipeline metadata so response conversion
455
550
  // can commit delivery only after a successful response is produced.
@@ -482,11 +577,11 @@ export class HubPipeline {
482
577
  annotatePassthroughGovernanceSkip(passthroughAudit);
483
578
  }
484
579
  }
485
- let workingRequest = processedRequest ?? standardizedRequest;
580
+ let workingRequest = syncResponsesContextFromCanonicalMessages(processedRequest ?? standardizedRequest);
486
581
  // 使用与 VirtualRouter 一致的 tiktoken 计数逻辑,对标准化请求进行一次
487
582
  // 上下文 token 估算,供后续 usage 归一化与统计使用。
488
583
  try {
489
- const estimatedTokens = computeRequestTokens(workingRequest, "");
584
+ const estimatedTokens = estimateSessionBoundTokens(workingRequest, normalized.metadata) ?? computeRequestTokens(workingRequest, "");
490
585
  if (typeof estimatedTokens === "number" &&
491
586
  Number.isFinite(estimatedTokens) &&
492
587
  estimatedTokens > 0) {
@@ -501,30 +596,9 @@ export class HubPipeline {
501
596
  const normalizedMeta = normalized.metadata;
502
597
  // responsesResume is a client-protocol semantic (/v1/responses tool loop) and must live in chat.semantics.
503
598
  // 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
- })();
599
+ const responsesResume = readResponsesResumeFromRequestSemantics(workingRequest);
523
600
  const stdMetadata = workingRequest?.metadata;
524
- const hasImageAttachment = stdMetadata?.hasImageAttachment === true ||
525
- stdMetadata?.hasImageAttachment === "true" ||
526
- normalizedMeta?.hasImageAttachment === true ||
527
- normalizedMeta?.hasImageAttachment === "true";
601
+ const hasImageAttachment = containsImageAttachment((workingRequest.messages ?? []));
528
602
  const serverToolRequired = stdMetadata?.webSearchEnabled === true ||
529
603
  stdMetadata?.serverToolRequired === true;
530
604
  const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
@@ -573,6 +647,7 @@ export class HubPipeline {
573
647
  : {}),
574
648
  ...stopMessageRouterMetadata,
575
649
  };
650
+ logHubStageTiming(normalized.id, "req_process.stage2_route_select", "start");
576
651
  const routing = runReqProcessStage2RouteSelect({
577
652
  routerEngine: this.routerEngine,
578
653
  request: workingRequest,
@@ -580,21 +655,7 @@ export class HubPipeline {
580
655
  normalizedMetadata: normalized.metadata,
581
656
  stageRecorder: inboundRecorder,
582
657
  });
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
- }
658
+ logHubStageTiming(normalized.id, "req_process.stage2_route_select", "completed");
598
659
  // Emit virtual router hit log for debugging (orange [virtual-router] ...)
599
660
  try {
600
661
  const routeName = routing.decision?.routeName;
@@ -660,23 +721,23 @@ export class HubPipeline {
660
721
  const outboundContextSnapshot = protocolSwitch
661
722
  ? undefined
662
723
  : contextSnapshot;
663
- const outboundStage1 = await runReqOutboundStage1SemanticMap({
724
+ const outboundStage1 = await measureHubStage(normalized.id, "req_outbound.stage1_semantic_map", () => runReqOutboundStage1SemanticMap({
664
725
  request: workingRequest,
665
726
  adapterContext: outboundAdapterContext,
666
727
  semanticMapper: outboundSemanticMapper,
667
728
  contextSnapshot: outboundContextSnapshot,
668
729
  contextMetadataKey: outboundContextMetadataKey,
669
730
  stageRecorder: outboundRecorder,
670
- });
671
- let formattedPayload = await runReqOutboundStage2FormatBuild({
731
+ }));
732
+ let formattedPayload = await measureHubStage(normalized.id, "req_outbound.stage2_format_build", () => runReqOutboundStage2FormatBuild({
672
733
  formatEnvelope: outboundStage1.formatEnvelope,
673
734
  stageRecorder: outboundRecorder,
674
- });
675
- formattedPayload = await runReqOutboundStage3Compat({
735
+ }));
736
+ formattedPayload = await measureHubStage(normalized.id, "req_outbound.stage3_compat", () => runReqOutboundStage3Compat({
676
737
  payload: formattedPayload,
677
738
  adapterContext: outboundAdapterContext,
678
739
  stageRecorder: outboundRecorder,
679
- });
740
+ }));
680
741
  if (shadowCompareBaselineMode) {
681
742
  const baselinePolicy = {
682
743
  ...(effectivePolicy ?? {}),
@@ -771,7 +832,7 @@ export class HubPipeline {
771
832
  //
772
833
  // 注意:这里不再根据 processMode(passthrough/chat) 做分支判断——即使某些
773
834
  // route 将 processMode 标记为 passthrough,我们仍然需要保留一次规范化后的
774
- // Chat 请求快照,供 stopMessage / empty_reply_continue 等被动触发型
835
+ // Chat 请求快照,供 stopMessage 等被动触发型
775
836
  // servertool 在响应阶段使用。
776
837
  //
777
838
  // 之前这里通过 JSON.stringify/parse 做深拷贝,但在部分 Responses/Gemini
@@ -796,7 +857,6 @@ export class HubPipeline {
796
857
  };
797
858
  const metadata = {
798
859
  ...normalized.metadata,
799
- ...(hasImageAttachment ? { hasImageAttachment: true } : {}),
800
860
  capturedChatRequest,
801
861
  entryEndpoint: normalized.entryEndpoint,
802
862
  providerProtocol: outboundProtocol,
@@ -819,6 +879,12 @@ export class HubPipeline {
819
879
  }
820
880
  : {}),
821
881
  };
882
+ if (hasImageAttachment) {
883
+ metadata.hasImageAttachment = true;
884
+ }
885
+ else {
886
+ delete metadata.hasImageAttachment;
887
+ }
822
888
  return {
823
889
  requestId: normalized.id,
824
890
  providerPayload,
@@ -956,25 +1022,11 @@ export class HubPipeline {
956
1022
  rtBase.clock = clockConfig;
957
1023
  }
958
1024
  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
- }
1025
+ const cleanedRequest = applyChatProcessEntryMediaCleanup(standardizedRequestBase);
1026
+ let standardizedRequest = cleanedRequest;
972
1027
  const activeProcessMode = resolveActiveProcessMode(normalized.processMode, cleanedRequest.messages);
973
1028
  if (activeProcessMode !== normalized.processMode) {
974
1029
  normalized.processMode = activeProcessMode;
975
- normalized.metadata = normalized.metadata || {};
976
- normalized.metadata.processMode =
977
- activeProcessMode;
978
1030
  }
979
1031
  const passthroughAudit = activeProcessMode === "passthrough"
980
1032
  ? buildPassthroughAudit(rawPayload, normalized.providerProtocol)
@@ -982,26 +1034,7 @@ export class HubPipeline {
982
1034
  // Semantic Gate (chat_process entry): lift any mappable protocol semantics from metadata into request.semantics.
983
1035
  // This is the last chance before entering chat_process; after this point we fail-fast on banned metadata keys.
984
1036
  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
- }
1037
+ standardizedRequest = liftResponsesResumeIntoSemantics(standardizedRequest, metaBase);
1005
1038
  }
1006
1039
  catch {
1007
1040
  // best-effort; validation happens below
@@ -1065,10 +1098,10 @@ export class HubPipeline {
1065
1098
  annotatePassthroughGovernanceSkip(passthroughAudit);
1066
1099
  }
1067
1100
  }
1068
- let workingRequest = processedRequest ?? standardizedRequest;
1101
+ let workingRequest = syncResponsesContextFromCanonicalMessages(processedRequest ?? standardizedRequest);
1069
1102
  // Token estimate for stats/diagnostics (best-effort).
1070
1103
  try {
1071
- const estimatedTokens = computeRequestTokens(workingRequest, "");
1104
+ const estimatedTokens = estimateSessionBoundTokens(workingRequest, normalized.metadata) ?? computeRequestTokens(workingRequest, "");
1072
1105
  if (typeof estimatedTokens === "number" &&
1073
1106
  Number.isFinite(estimatedTokens) &&
1074
1107
  estimatedTokens > 0) {
@@ -1083,30 +1116,9 @@ export class HubPipeline {
1083
1116
  const normalizedMeta = normalized.metadata;
1084
1117
  // responsesResume is a client-protocol semantic (/v1/responses tool loop) and must live in chat.semantics.
1085
1118
  // 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
- })();
1119
+ const responsesResume = readResponsesResumeFromRequestSemantics(workingRequest);
1105
1120
  const stdMetadata = workingRequest?.metadata;
1106
- const hasImageAttachment = stdMetadata?.hasImageAttachment === true ||
1107
- stdMetadata?.hasImageAttachment === "true" ||
1108
- normalizedMeta?.hasImageAttachment === true ||
1109
- normalizedMeta?.hasImageAttachment === "true";
1121
+ const hasImageAttachment = containsImageAttachment((workingRequest.messages ?? []));
1110
1122
  const serverToolRequired = stdMetadata?.webSearchEnabled === true ||
1111
1123
  stdMetadata?.serverToolRequired === true;
1112
1124
  const sessionIdentifiers = extractSessionIdentifiersFromMetadata(normalized.metadata);
@@ -1151,21 +1163,6 @@ export class HubPipeline {
1151
1163
  normalizedMetadata: normalized.metadata,
1152
1164
  stageRecorder,
1153
1165
  });
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
- }
1169
1166
  // Emit virtual router hit log for debugging (same as inbound path).
1170
1167
  try {
1171
1168
  const routeName = routing.decision?.routeName;
@@ -1224,23 +1221,23 @@ export class HubPipeline {
1224
1221
  ? outboundHooks.contextMetadataKey
1225
1222
  : hooks.contextMetadataKey;
1226
1223
  const outboundContextSnapshot = undefined;
1227
- const outboundStage1 = await runReqOutboundStage1SemanticMap({
1224
+ const outboundStage1 = await measureHubStage(normalized.id, "req_outbound.stage1_semantic_map", () => runReqOutboundStage1SemanticMap({
1228
1225
  request: workingRequest,
1229
1226
  adapterContext: outboundAdapterContext,
1230
1227
  semanticMapper: outboundSemanticMapper,
1231
1228
  contextSnapshot: outboundContextSnapshot,
1232
1229
  contextMetadataKey: outboundContextMetadataKey,
1233
1230
  stageRecorder: outboundRecorder,
1234
- });
1235
- let formattedPayload = await runReqOutboundStage2FormatBuild({
1231
+ }));
1232
+ let formattedPayload = await measureHubStage(normalized.id, "req_outbound.stage2_format_build", () => runReqOutboundStage2FormatBuild({
1236
1233
  formatEnvelope: outboundStage1.formatEnvelope,
1237
1234
  stageRecorder: outboundRecorder,
1238
- });
1239
- formattedPayload = await runReqOutboundStage3Compat({
1235
+ }));
1236
+ formattedPayload = await measureHubStage(normalized.id, "req_outbound.stage3_compat", () => runReqOutboundStage3Compat({
1240
1237
  payload: formattedPayload,
1241
1238
  adapterContext: outboundAdapterContext,
1242
1239
  stageRecorder: outboundRecorder,
1243
- });
1240
+ }));
1244
1241
  // Phase 0/1: observe + enforce provider outbound policy and tool surface (same as inbound path).
1245
1242
  const effectivePolicy = normalized.policyOverride ?? this.config.policy;
1246
1243
  recordHubPolicyObservation({
@@ -1312,7 +1309,6 @@ export class HubPipeline {
1312
1309
  };
1313
1310
  const metadata = {
1314
1311
  ...normalized.metadata,
1315
- ...(hasImageAttachment ? { hasImageAttachment: true } : {}),
1316
1312
  capturedChatRequest,
1317
1313
  entryEndpoint: normalized.entryEndpoint,
1318
1314
  providerProtocol: outboundProtocol,
@@ -1325,6 +1321,12 @@ export class HubPipeline {
1325
1321
  ? { providerStream: outboundStream }
1326
1322
  : {}),
1327
1323
  };
1324
+ if (hasImageAttachment) {
1325
+ metadata.hasImageAttachment = true;
1326
+ }
1327
+ else {
1328
+ delete metadata.hasImageAttachment;
1329
+ }
1328
1330
  return {
1329
1331
  requestId: normalized.id,
1330
1332
  providerPayload,
@@ -1759,11 +1761,9 @@ export class HubPipeline {
1759
1761
  direction,
1760
1762
  stage,
1761
1763
  stream,
1764
+ ...(routeHint ? { routeHint } : {}),
1762
1765
  ...(orchestrationResult.metadata ?? {}),
1763
1766
  };
1764
- if (routeHint) {
1765
- normalizedMetadata.routeHint = routeHint;
1766
- }
1767
1767
  return {
1768
1768
  id,
1769
1769
  endpoint,
@@ -0,0 +1,6 @@
1
+ export declare function logHubStageTiming(requestId: string, stage: string, phase: 'start' | 'completed' | 'error', details?: Record<string, unknown>): void;
2
+ export declare function measureHubStage<T>(requestId: string, stage: string, fn: () => Promise<T> | T, options?: {
3
+ startDetails?: Record<string, unknown>;
4
+ mapCompletedDetails?: (value: T) => Record<string, unknown> | undefined;
5
+ mapErrorDetails?: (error: unknown) => Record<string, unknown> | undefined;
6
+ }): Promise<T>;