@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,18 +1,108 @@
1
- import { analyzeChatProcessMedia, stripChatProcessHistoricalImages } from '../../../router/virtual-router/engine-selection/native-router-hotpath.js';
1
+ import { analyzeChatProcessMedia, stripChatProcessHistoricalImages, } from "../../../router/virtual-router/engine-selection/native-router-hotpath.js";
2
2
  export function stripHistoricalImageAttachments(messages) {
3
3
  if (!Array.isArray(messages) || !messages.length) {
4
4
  return messages;
5
5
  }
6
- const placeholderText = '[Image omitted]';
6
+ const placeholderText = "[Image omitted]";
7
7
  const stripped = stripChatProcessHistoricalImages(messages, placeholderText);
8
8
  if (stripped.changed !== true || !Array.isArray(stripped.messages)) {
9
9
  return messages;
10
10
  }
11
11
  return stripped.messages;
12
12
  }
13
+ const INLINE_MEDIA_DATA_RE = /data:(image|video)\/[a-z0-9.+-]+;base64,[a-z0-9+/=\s]+/i;
14
+ function isVisualToolMessage(message) {
15
+ if (!message || typeof message !== "object") {
16
+ return false;
17
+ }
18
+ if (message.role !== "tool") {
19
+ return false;
20
+ }
21
+ const name = typeof message.name === "string" ? message.name.trim().toLowerCase() : "";
22
+ if (name === "view_image") {
23
+ return true;
24
+ }
25
+ const content = typeof message.content === "string" ? message.content : "";
26
+ return INLINE_MEDIA_DATA_RE.test(content);
27
+ }
28
+ export function stripHistoricalVisualToolOutputs(messages) {
29
+ if (!Array.isArray(messages) || messages.length === 0) {
30
+ return messages;
31
+ }
32
+ let changed = false;
33
+ const next = messages.map((message) => {
34
+ if (!isVisualToolMessage(message)) {
35
+ return message;
36
+ }
37
+ const content = typeof message.content === "string" ? message.content : "";
38
+ if (!INLINE_MEDIA_DATA_RE.test(content)) {
39
+ return message;
40
+ }
41
+ changed = true;
42
+ return {
43
+ ...message,
44
+ content: "[Image omitted]",
45
+ };
46
+ });
47
+ return changed ? next : messages;
48
+ }
13
49
  export function containsImageAttachment(messages) {
14
50
  if (!Array.isArray(messages) || !messages.length) {
15
51
  return false;
16
52
  }
17
53
  return analyzeChatProcessMedia(messages).containsCurrentTurnImage === true;
18
54
  }
55
+ export function repairIncompleteToolCalls(messages) {
56
+ if (!Array.isArray(messages) || messages.length === 0) {
57
+ return messages;
58
+ }
59
+ const toolCallIdsWithResponse = new Set();
60
+ for (let i = messages.length - 1; i >= 0; i--) {
61
+ const msg = messages[i];
62
+ if (msg?.role === "tool" && typeof msg.tool_call_id === "string") {
63
+ toolCallIdsWithResponse.add(msg.tool_call_id);
64
+ }
65
+ }
66
+ const result = [];
67
+ let changed = false;
68
+ for (const msg of messages) {
69
+ if (msg?.role === "assistant" && Array.isArray(msg.tool_calls)) {
70
+ const completeToolCalls = [];
71
+ const missingToolCallIds = [];
72
+ for (const tc of msg.tool_calls) {
73
+ const tcId = typeof tc?.id === "string" ? tc.id : "";
74
+ if (tcId && toolCallIdsWithResponse.has(tcId)) {
75
+ completeToolCalls.push(tc);
76
+ }
77
+ else if (tcId) {
78
+ missingToolCallIds.push(tcId);
79
+ }
80
+ }
81
+ if (missingToolCallIds.length > 0) {
82
+ changed = true;
83
+ const repaired = {
84
+ ...msg,
85
+ tool_calls: completeToolCalls.length > 0 ? completeToolCalls : undefined,
86
+ };
87
+ if (!repaired.tool_calls) {
88
+ delete repaired.tool_calls;
89
+ }
90
+ result.push(repaired);
91
+ for (const missingId of missingToolCallIds) {
92
+ result.push({
93
+ role: "tool",
94
+ tool_call_id: missingId,
95
+ content: '{"status":"tool_call_repaired_orphaned_tool_call"}',
96
+ });
97
+ }
98
+ }
99
+ else {
100
+ result.push(msg);
101
+ }
102
+ }
103
+ else {
104
+ result.push(msg);
105
+ }
106
+ }
107
+ return changed ? result : messages;
108
+ }
@@ -0,0 +1,7 @@
1
+ import type { AdapterContext } from '../types/chat-envelope.js';
2
+ import type { ProcessedRequest, StandardizedRequest } from '../types/standardized.js';
3
+ export declare function estimateSessionBoundTokens(request: StandardizedRequest | ProcessedRequest, metadata: Record<string, unknown> | undefined): number | undefined;
4
+ export declare function saveChatProcessSessionActualUsage(options: {
5
+ context: AdapterContext;
6
+ usage: Record<string, unknown> | undefined;
7
+ }): void;
@@ -0,0 +1,147 @@
1
+ import { loadRoutingInstructionStateSync, saveRoutingInstructionStateSync } from '../../../router/virtual-router/sticky-session-store.js';
2
+ import { countRequestTokens } from '../../../router/virtual-router/token-counter.js';
3
+ function createEmptyRoutingInstructionState() {
4
+ return {
5
+ allowedProviders: new Set(),
6
+ disabledProviders: new Set(),
7
+ disabledKeys: new Map(),
8
+ disabledModels: new Map()
9
+ };
10
+ }
11
+ function resolveSessionUsageScope(record) {
12
+ const sessionId = typeof record?.sessionId === 'string' ? record.sessionId.trim() : '';
13
+ if (sessionId) {
14
+ return `session:${sessionId}`;
15
+ }
16
+ const conversationId = typeof record?.conversationId === 'string' ? record.conversationId.trim() : '';
17
+ if (conversationId) {
18
+ return `conversation:${conversationId}`;
19
+ }
20
+ return undefined;
21
+ }
22
+ function loadState(scope) {
23
+ try {
24
+ return loadRoutingInstructionStateSync(scope);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ function readRoundedToken(value) {
31
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
32
+ return undefined;
33
+ }
34
+ const rounded = Math.round(value);
35
+ return rounded > 0 ? rounded : undefined;
36
+ }
37
+ function buildSnapshot(scope, state) {
38
+ if (!state) {
39
+ return null;
40
+ }
41
+ const totalTokens = readRoundedToken(state.chatProcessLastTotalTokens);
42
+ const inputTokens = readRoundedToken(state.chatProcessLastInputTokens);
43
+ const messageCount = readRoundedToken(state.chatProcessLastMessageCount);
44
+ const updatedAtMs = readRoundedToken(state.chatProcessLastUpdatedAt);
45
+ if (totalTokens === undefined && inputTokens === undefined) {
46
+ return null;
47
+ }
48
+ return {
49
+ scope,
50
+ ...(totalTokens !== undefined ? { totalTokens } : {}),
51
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
52
+ ...(messageCount !== undefined ? { messageCount } : {}),
53
+ ...(updatedAtMs !== undefined ? { updatedAtMs } : {})
54
+ };
55
+ }
56
+ function normalizeUsage(usage) {
57
+ if (!usage || typeof usage !== 'object') {
58
+ return null;
59
+ }
60
+ const inputTokens = readRoundedToken(usage.input_tokens ??
61
+ usage.prompt_tokens ??
62
+ usage.inputTokens ??
63
+ usage.promptTokens ??
64
+ usage.request_tokens ??
65
+ usage.requestTokens);
66
+ const outputTokens = readRoundedToken(usage.output_tokens ??
67
+ usage.completion_tokens ??
68
+ usage.outputTokens ??
69
+ usage.completionTokens ??
70
+ usage.response_tokens ??
71
+ usage.responseTokens);
72
+ const totalTokens = readRoundedToken(usage.total_tokens ??
73
+ usage.totalTokens ??
74
+ ((inputTokens ?? 0) + (outputTokens ?? 0) > 0
75
+ ? (inputTokens ?? 0) + (outputTokens ?? 0)
76
+ : undefined));
77
+ if (totalTokens === undefined && inputTokens === undefined) {
78
+ return null;
79
+ }
80
+ return {
81
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
82
+ ...(outputTokens !== undefined ? { outputTokens } : {}),
83
+ ...(totalTokens !== undefined ? { totalTokens } : {})
84
+ };
85
+ }
86
+ function estimateDeltaTokens(request, previousMessageCount) {
87
+ const messages = Array.isArray(request.messages) ? request.messages : [];
88
+ if (previousMessageCount < 0 || previousMessageCount > messages.length) {
89
+ return undefined;
90
+ }
91
+ const appendedMessages = messages.slice(previousMessageCount);
92
+ if (appendedMessages.length === 0) {
93
+ return 0;
94
+ }
95
+ return countRequestTokens({
96
+ model: request.model,
97
+ messages: appendedMessages,
98
+ parameters: {},
99
+ metadata: { originalEndpoint: request.metadata?.originalEndpoint ?? '/v1/chat/completions' }
100
+ });
101
+ }
102
+ export function estimateSessionBoundTokens(request, metadata) {
103
+ const scope = resolveSessionUsageScope(metadata);
104
+ if (!scope) {
105
+ return undefined;
106
+ }
107
+ const snapshot = buildSnapshot(scope, loadState(scope));
108
+ if (!snapshot) {
109
+ return undefined;
110
+ }
111
+ const previousTotal = snapshot.totalTokens ?? snapshot.inputTokens;
112
+ const previousMessageCount = snapshot.messageCount;
113
+ if (previousTotal === undefined || previousMessageCount === undefined) {
114
+ return undefined;
115
+ }
116
+ const deltaTokens = estimateDeltaTokens(request, previousMessageCount);
117
+ if (deltaTokens === undefined) {
118
+ return undefined;
119
+ }
120
+ return Math.max(1, Math.round(previousTotal + deltaTokens));
121
+ }
122
+ export function saveChatProcessSessionActualUsage(options) {
123
+ const scope = resolveSessionUsageScope(options.context);
124
+ if (!scope) {
125
+ return;
126
+ }
127
+ const normalizedUsage = normalizeUsage(options.usage);
128
+ if (!normalizedUsage) {
129
+ return;
130
+ }
131
+ const capturedChatRequest = options.context.capturedChatRequest;
132
+ const messageCount = Array.isArray(capturedChatRequest?.messages)
133
+ ? (capturedChatRequest.messages ?? []).length
134
+ : undefined;
135
+ const state = loadState(scope) ?? createEmptyRoutingInstructionState();
136
+ if (normalizedUsage.totalTokens !== undefined) {
137
+ state.chatProcessLastTotalTokens = normalizedUsage.totalTokens;
138
+ }
139
+ if (normalizedUsage.inputTokens !== undefined) {
140
+ state.chatProcessLastInputTokens = normalizedUsage.inputTokens;
141
+ }
142
+ if (typeof messageCount === 'number' && Number.isFinite(messageCount)) {
143
+ state.chatProcessLastMessageCount = Math.max(0, Math.round(messageCount));
144
+ }
145
+ state.chatProcessLastUpdatedAt = Date.now();
146
+ saveRoutingInstructionStateSync(scope, state);
147
+ }
@@ -19,6 +19,7 @@ import { ProviderProtocolError } from '../../provider-protocol-error.js';
19
19
  import { readRuntimeMetadata } from '../../runtime-metadata.js';
20
20
  import { commitClockReservation, resolveClockConfig } from '../../../servertool/clock/task-store.js';
21
21
  import { detectProviderResponseShapeWithNative } from '../../../router/virtual-router/engine-selection/native-chat-process-servertool-orchestration-semantics.js';
22
+ import { saveChatProcessSessionActualUsage } from '../process/chat-process-session-usage.js';
22
23
  const PROVIDER_RESPONSE_REGISTRY = {
23
24
  'openai-chat': {
24
25
  createFormatAdapter: () => new ChatFormatAdapter(),
@@ -451,6 +452,18 @@ export async function convertProviderResponse(options) {
451
452
  });
452
453
  // Commit scheduled-task delivery only after a successful client payload/stream is prepared.
453
454
  await maybeCommitClockReservationFromContext(options.context);
455
+ try {
456
+ const usage = clientPayload && typeof clientPayload === 'object' && !Array.isArray(clientPayload)
457
+ ? clientPayload.usage
458
+ : undefined;
459
+ saveChatProcessSessionActualUsage({
460
+ context: options.context,
461
+ usage
462
+ });
463
+ }
464
+ catch {
465
+ // best-effort: usage persistence must not break response delivery
466
+ }
454
467
  if (outbound.stream) {
455
468
  const usage = clientPayload && typeof clientPayload === 'object' && !Array.isArray(clientPayload)
456
469
  ? clientPayload.usage
@@ -55,10 +55,6 @@ function collectRetentionContext(context) {
55
55
  const stripHostManagedFields = shouldStripHostManagedFields(context);
56
56
  return {
57
57
  metadata: context?.metadata,
58
- parallelToolCalls: context?.parallel_tool_calls,
59
- toolChoice: context?.tool_choice,
60
- include: context?.include,
61
- store: context?.store,
62
58
  stripHostManagedFields
63
59
  };
64
60
  }
@@ -103,10 +99,6 @@ export function buildResponsesPayloadFromChat(payload, context) {
103
99
  requestId: context?.requestId,
104
100
  toolsRaw: Array.isArray(context?.toolsRaw) ? context?.toolsRaw : [],
105
101
  metadata: retentionContext.metadata,
106
- parallelToolCalls: retentionContext.parallelToolCalls,
107
- toolChoice: retentionContext.toolChoice,
108
- include: retentionContext.include,
109
- store: retentionContext.store,
110
102
  stripHostManagedFields: retentionContext.stripHostManagedFields,
111
103
  sourceForRetention: sourceForRetention
112
104
  });
@@ -176,10 +168,6 @@ export function buildResponsesPayloadFromChat(payload, context) {
176
168
  requestId: context?.requestId,
177
169
  toolsRaw: Array.isArray(context?.toolsRaw) ? context?.toolsRaw : [],
178
170
  metadata: retentionContext.metadata,
179
- parallelToolCalls: retentionContext.parallelToolCalls,
180
- toolChoice: retentionContext.toolChoice,
181
- include: retentionContext.include,
182
- store: retentionContext.store,
183
171
  stripHostManagedFields: retentionContext.stripHostManagedFields,
184
172
  sourceForRetention: sourceForRetention
185
173
  });
@@ -1,6 +1,6 @@
1
1
  import type { BridgeInputItem, BridgeToolDefinition } from '../../types/bridge-message-types.js';
2
2
  import type { ChatToolDefinition } from '../../hub/types/chat-envelope.js';
3
- import type { JsonObject, JsonValue } from '../../hub/types/json.js';
3
+ import type { JsonObject } from '../../hub/types/json.js';
4
4
  import type { ToolCallIdStyle } from '../../shared/responses-tool-utils.js';
5
5
  export type Unknown = Record<string, unknown>;
6
6
  export interface ResponsesRequestContext extends Unknown {
@@ -8,15 +8,7 @@ export interface ResponsesRequestContext extends Unknown {
8
8
  targetProtocol?: string;
9
9
  originalSystemMessages?: string[];
10
10
  input?: BridgeInputItem[];
11
- include?: unknown;
12
- store?: unknown;
13
- serviceTier?: unknown;
14
- truncation?: unknown;
15
- toolChoice?: unknown;
16
- parallelToolCalls?: boolean;
17
11
  metadata?: JsonObject;
18
- responseFormat?: JsonValue;
19
- stream?: boolean;
20
12
  isChatPayload?: boolean;
21
13
  isResponsesPayload?: boolean;
22
14
  historyMessages?: Array<{
@@ -4,10 +4,12 @@ import { convertBridgeInputToChatMessages } from '../bridge-message-utils.js';
4
4
  import { createToolCallIdTransformer, enforceToolCallIdStyle, sanitizeResponsesFunctionName } from '../shared/responses-tool-utils.js';
5
5
  import { mapChatToolsToBridge } from '../shared/tool-mapping.js';
6
6
  import { ProviderProtocolError } from '../provider-protocol-error.js';
7
+ import { isJsonObject, jsonClone } from '../hub/types/json.js';
7
8
  import { captureReqInboundResponsesContextSnapshotWithNative, mapReqInboundBridgeToolsToChatWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
8
9
  import { appendLocalImageBlockOnLatestUserInputWithNative, buildBridgeHistoryWithNative, filterBridgeInputForUpstreamWithNative, normalizeBridgeHistorySeedWithNative, prepareResponsesRequestEnvelopeWithNative, resolveResponsesRequestBridgeDecisionsWithNative, resolveResponsesBridgeToolsWithNative, runBridgeActionPipelineWithNative } from '../../router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js';
9
10
  // --- Utilities (ported strictly) ---
10
11
  import { resolveBridgePolicy, resolvePolicyActions } from '../bridge-policies.js';
12
+ import { logHubStageTiming } from '../hub/pipeline/hub-stage-timing.js';
11
13
  function isObject(v) {
12
14
  return !!v && typeof v === 'object' && !Array.isArray(v);
13
15
  }
@@ -37,18 +39,22 @@ function runNativeResponsesBridgePipeline(input) {
37
39
  : undefined
38
40
  };
39
41
  }
42
+ function filterRedundantResponsesReasoningAction(actions) {
43
+ return actions?.filter((action) => {
44
+ const name = typeof action?.name === 'string' ? action.name.trim().toLowerCase() : '';
45
+ return name !== 'reasoning.extract';
46
+ });
47
+ }
40
48
  // normalizeTools unified in ../args-mapping.ts
41
49
  // NOTE: 自修复提示已移除(统一标准:不做模糊兜底)。
42
50
  // --- Public bridge functions ---
43
51
  export function captureResponsesContext(payload, dto) {
44
- const preservedInput = cloneBridgeEntries(payload.input);
52
+ const preservedInput = Array.isArray(payload.input)
53
+ ? payload.input
54
+ : undefined;
45
55
  ensureBridgeInstructions(payload);
46
- const requestForCapture = {
47
- ...payload,
48
- ...(preservedInput ? { input: preservedInput } : {})
49
- };
50
56
  const captured = captureReqInboundResponsesContextSnapshotWithNative({
51
- rawRequest: requestForCapture,
57
+ rawRequest: payload,
52
58
  requestId: dto?.route?.requestId,
53
59
  toolCallIdStyle: payload?.toolCallIdStyle ?? payload?.metadata?.toolCallIdStyle
54
60
  });
@@ -68,26 +74,42 @@ export function captureResponsesContext(payload, dto) {
68
74
  if (!captured.systemInstruction && typeof payload.instructions === 'string' && payload.instructions.trim().length) {
69
75
  captured.systemInstruction = payload.instructions;
70
76
  }
77
+ if (captured.metadata && isJsonObject(captured.metadata)) {
78
+ const cloned = jsonClone(captured.metadata);
79
+ delete cloned.extraFields;
80
+ captured.metadata = cloned;
81
+ }
71
82
  return captured;
72
83
  }
73
84
  export function buildChatRequestFromResponses(payload, context) {
85
+ const requestId = typeof context.requestId === 'string' && context.requestId.trim().length
86
+ ? context.requestId
87
+ : 'unknown';
74
88
  // V3: 对 Responses 路径仅做“形状转换”,不做参数解析/修复。
75
89
  // 将顶层 { type,name,description,parameters,strict } 归一为 OpenAI Chat tools 形状:
76
90
  // { type:'function', function:{ name,description,parameters,strict? } }
77
91
  const toolsNormalized = Array.isArray(context.toolsNormalized) && context.toolsNormalized.length
78
- ? cloneBridgeEntries(context.toolsNormalized)
92
+ ? context.toolsNormalized
79
93
  : mapReqInboundBridgeToolsToChatWithNative(payload.tools);
80
94
  // 不在 Responses 路径进行 MCP 工具注入;统一由 Chat 后半段治理注入
95
+ logHubStageTiming(requestId, 'req_inbound.responses.convert_input_to_messages', 'start');
96
+ const convertStart = Date.now();
81
97
  let messages = convertBridgeInputToChatMessages({
82
98
  input: context.input,
83
99
  tools: toolsNormalized,
84
100
  normalizeFunctionName: 'responses',
85
101
  toolResultFallbackText: 'Command succeeded (no output).'
86
102
  });
103
+ logHubStageTiming(requestId, 'req_inbound.responses.convert_input_to_messages', 'completed', {
104
+ elapsedMs: Date.now() - convertStart,
105
+ forceLog: true
106
+ });
87
107
  try {
88
108
  const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
89
- const policyActions = resolvePolicyActions(bridgePolicy, 'request_inbound');
109
+ const policyActions = filterRedundantResponsesReasoningAction(resolvePolicyActions(bridgePolicy, 'request_inbound'));
90
110
  if (policyActions?.length) {
111
+ logHubStageTiming(requestId, 'req_inbound.responses.inbound_policy', 'start');
112
+ const policyStart = Date.now();
91
113
  const actionState = runNativeResponsesBridgePipeline({
92
114
  stage: 'request_inbound',
93
115
  actions: policyActions,
@@ -99,6 +121,10 @@ export function buildChatRequestFromResponses(payload, context) {
99
121
  rawRequest: payload
100
122
  });
101
123
  messages = actionState.messages;
124
+ logHubStageTiming(requestId, 'req_inbound.responses.inbound_policy', 'completed', {
125
+ elapsedMs: Date.now() - policyStart,
126
+ forceLog: true
127
+ });
102
128
  }
103
129
  }
104
130
  catch {
@@ -174,7 +200,7 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
174
200
  let bridgeMetadata;
175
201
  try {
176
202
  const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
177
- const policyActions = resolvePolicyActions(bridgePolicy, 'request_outbound');
203
+ const policyActions = filterRedundantResponsesReasoningAction(resolvePolicyActions(bridgePolicy, 'request_outbound'));
178
204
  if (policyActions?.length) {
179
205
  const actionState = runNativeResponsesBridgePipeline({
180
206
  stage: 'request_outbound',
@@ -270,29 +296,27 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
270
296
  metadataSystemInstruction: envelopeMetadata?.systemInstruction,
271
297
  combinedSystemInstruction,
272
298
  reasoningInstructionSegments: ctx?.__rcc_reasoning_instructions_segments,
273
- contextParameters: ctx?.parameters,
299
+ contextParameters: undefined,
274
300
  chatParameters: chat.parameters,
275
- metadataParameters: metadataExtraFields?.parameters,
276
- contextStream: ctx?.stream,
277
- metadataStream: metadataExtraFields?.stream,
301
+ metadataParameters: stripToolControlFieldsFromParameterObject(metadataExtraFields?.parameters),
302
+ contextStream: undefined,
303
+ metadataStream: undefined,
278
304
  chatStream: streamFromChat,
279
305
  chatParametersStream: streamFromParameters,
280
- contextInclude: ctx?.include,
281
- metadataInclude: metadataExtraFields?.include,
282
- contextStore: ctx?.store,
283
- metadataStore: metadataExtraFields?.store,
306
+ contextInclude: undefined,
307
+ metadataInclude: undefined,
308
+ contextStore: undefined,
309
+ metadataStore: undefined,
284
310
  stripHostFields,
285
- contextToolChoice: ctx?.toolChoice,
286
- metadataToolChoice: metadataExtraFields?.tool_choice,
287
- contextParallelToolCalls: ctx?.parallelToolCalls,
288
- metadataParallelToolCalls: metadataExtraFields?.parallel_tool_calls,
289
- contextResponseFormat: ctx?.responseFormat,
290
- metadataResponseFormat: metadataExtraFields?.response_format,
291
- contextServiceTier: ctx?.serviceTier,
292
- metadataServiceTier: metadataExtraFields?.service_tier,
293
- contextTruncation: ctx?.truncation,
294
- metadataTruncation: metadataExtraFields?.truncation,
295
- contextMetadata: ctx?.metadata,
311
+ contextToolChoice: undefined,
312
+ contextParallelToolCalls: undefined,
313
+ contextResponseFormat: undefined,
314
+ metadataResponseFormat: undefined,
315
+ contextServiceTier: undefined,
316
+ metadataServiceTier: undefined,
317
+ contextTruncation: undefined,
318
+ metadataTruncation: undefined,
319
+ contextMetadata: stripToolControlFieldsFromContextMetadata(ctx?.metadata),
296
320
  metadataMetadata: metadataExtraFields?.metadata
297
321
  });
298
322
  Object.assign(out, preparedEnvelope.request);
@@ -300,22 +324,6 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
300
324
  ensureBridgeInstructions(out);
301
325
  return { request: out, originalSystemMessages };
302
326
  }
303
- function cloneBridgeEntries(entries) {
304
- if (!Array.isArray(entries)) {
305
- return undefined;
306
- }
307
- return entries.map((entry) => {
308
- if (!entry || typeof entry !== 'object') {
309
- return entry;
310
- }
311
- try {
312
- return JSON.parse(JSON.stringify(entry));
313
- }
314
- catch {
315
- return entry;
316
- }
317
- });
318
- }
319
327
  function extractMetadataExtraFields(metadata) {
320
328
  if (!metadata) {
321
329
  return undefined;
@@ -326,6 +334,31 @@ function extractMetadataExtraFields(metadata) {
326
334
  }
327
335
  return undefined;
328
336
  }
337
+ function stripToolControlFieldsFromContextMetadata(metadata) {
338
+ if (!metadata) {
339
+ return undefined;
340
+ }
341
+ const cloned = jsonClone(metadata);
342
+ const extras = cloned.extraFields;
343
+ if (!extras || !isPlainObject(extras)) {
344
+ return cloned;
345
+ }
346
+ delete extras.tool_choice;
347
+ delete extras.parallel_tool_calls;
348
+ if (Object.keys(extras).length === 0) {
349
+ delete cloned.extraFields;
350
+ }
351
+ return cloned;
352
+ }
353
+ function stripToolControlFieldsFromParameterObject(value) {
354
+ if (!value) {
355
+ return undefined;
356
+ }
357
+ const cloned = jsonClone(value);
358
+ delete cloned.tool_choice;
359
+ delete cloned.parallel_tool_calls;
360
+ return Object.keys(cloned).length ? cloned : undefined;
361
+ }
329
362
  function isPlainObject(value) {
330
363
  return Boolean(value && typeof value === 'object' && !Array.isArray(value));
331
364
  }
@@ -1,5 +1,40 @@
1
1
  import { normalizeReasoningInAnthropicPayloadWithNative, normalizeReasoningInChatPayloadWithNative, normalizeReasoningInGeminiPayloadWithNative, normalizeReasoningInOpenAIPayloadWithNative, normalizeReasoningInResponsesPayloadWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
2
2
  export const RESPONSES_INSTRUCTIONS_REASONING_FIELD = '__rcc_reasoning_instructions';
3
+ const REASONING_TEXT_MARKERS = [
4
+ '<think',
5
+ '</think',
6
+ '<reflection',
7
+ '</reflection',
8
+ '```think',
9
+ '```reflection'
10
+ ];
11
+ function stringHasReasoningMarker(value) {
12
+ const lower = value.toLowerCase();
13
+ return REASONING_TEXT_MARKERS.some((marker) => lower.includes(marker));
14
+ }
15
+ function valueMayContainReasoningMarkup(value) {
16
+ if (typeof value === 'string') {
17
+ return stringHasReasoningMarker(value);
18
+ }
19
+ if (!value || typeof value !== 'object') {
20
+ return false;
21
+ }
22
+ if (Array.isArray(value)) {
23
+ for (const entry of value) {
24
+ if (valueMayContainReasoningMarkup(entry)) {
25
+ return true;
26
+ }
27
+ }
28
+ return false;
29
+ }
30
+ const record = value;
31
+ for (const entry of Object.values(record)) {
32
+ if (valueMayContainReasoningMarkup(entry)) {
33
+ return true;
34
+ }
35
+ }
36
+ return false;
37
+ }
3
38
  function assertReasoningNormalizerNativeAvailable() {
4
39
  if (typeof normalizeReasoningInChatPayloadWithNative !== 'function' ||
5
40
  typeof normalizeReasoningInResponsesPayloadWithNative !== 'function' ||
@@ -22,6 +57,13 @@ export function normalizeReasoningInResponsesPayload(payload, options = { includ
22
57
  assertReasoningNormalizerNativeAvailable();
23
58
  if (!payload)
24
59
  return;
60
+ const shouldNormalize = (options.includeInput === true && valueMayContainReasoningMarkup(payload.input)) ||
61
+ (options.includeOutput === true && valueMayContainReasoningMarkup(payload.output)) ||
62
+ (options.includeInstructions === true && valueMayContainReasoningMarkup(payload.instructions)) ||
63
+ (options.includeRequiredAction === true && valueMayContainReasoningMarkup(payload.required_action));
64
+ if (!shouldNormalize) {
65
+ return;
66
+ }
25
67
  const normalized = normalizeReasoningInResponsesPayloadWithNative(payload, options);
26
68
  if (normalized && typeof normalized === 'object') {
27
69
  Object.assign(payload, normalized);
@@ -94,9 +94,8 @@ export function enforceToolCallIdStyle(input, transformer) {
94
94
  if (type === 'function_call_output' || type === 'tool_result' || type === 'tool_message') {
95
95
  const normalizedCallId = transformer.normalizeCallId(entry.call_id ?? entry.tool_call_id ?? entry.id);
96
96
  entry.call_id = normalizedCallId;
97
- if (entry.tool_call_id !== undefined) {
98
- delete entry.tool_call_id;
99
- }
97
+ // Keep tool_call_id for providers that expect it (e.g., Qwen)
98
+ entry.tool_call_id = normalizedCallId;
100
99
  entry.id = transformer.normalizeOutputId(normalizedCallId, entry.id);
101
100
  }
102
101
  }
@@ -22,6 +22,7 @@ export function buildProviderProfiles(targetKeys, runtimeEntries) {
22
22
  providerType: runtime.providerType,
23
23
  endpoint: runtime.endpoint,
24
24
  auth: { ...runtime.auth },
25
+ ...(runtime.enabled !== undefined ? { enabled: runtime.enabled } : {}),
25
26
  outboundProfile: runtime.outboundProfile,
26
27
  compatibilityProfile: runtime.compatibilityProfile,
27
28
  runtimeKey,
@@ -12,6 +12,7 @@ export interface NormalizedProvider {
12
12
  providerType: string;
13
13
  endpoint: string;
14
14
  headers?: Record<string, string>;
15
+ enabled?: boolean;
15
16
  outboundProfile: string;
16
17
  compatibilityProfile: string;
17
18
  processMode: 'chat' | 'passthrough';
@@ -11,6 +11,11 @@ import { normalizeModelStreaming, normalizeModelContextTokens, normalizeModelOut
11
11
  */
12
12
  export function normalizeProvider(providerId, raw) {
13
13
  const provider = asRecord(raw);
14
+ const enabled = typeof provider.enabled === 'boolean'
15
+ ? provider.enabled
16
+ : typeof provider.enabled === 'string'
17
+ ? provider.enabled.trim().toLowerCase() !== 'false'
18
+ : undefined;
14
19
  const providerType = detectProviderType(provider);
15
20
  const endpoint = typeof provider.endpoint === 'string' && provider.endpoint.trim()
16
21
  ? provider.endpoint.trim()
@@ -46,6 +51,7 @@ export function normalizeProvider(providerId, raw) {
46
51
  providerType,
47
52
  endpoint,
48
53
  headers,
54
+ ...(enabled !== undefined ? { enabled } : {}),
49
55
  outboundProfile: mapOutboundProfile(providerType),
50
56
  compatibilityProfile,
51
57
  processMode,