@jsonstudio/llms 0.6.3685 → 0.6.3688

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 (65) hide show
  1. package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +2 -22
  2. package/dist/conversion/compat/actions/deepseek-web-response.js +7 -0
  3. package/dist/conversion/compat/actions/field-mapping.js +153 -2
  4. package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.js +104 -3
  5. package/dist/conversion/hub/node-support.js +1 -1
  6. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +1 -9
  7. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +28 -35
  8. package/dist/conversion/hub/pipeline/hub-pipeline.js +121 -197
  9. package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +1 -0
  10. package/dist/conversion/hub/pipeline/hub-stage-timing.js +14 -8
  11. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +4 -4
  12. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +37 -20
  13. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -6
  14. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +41 -69
  15. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.d.ts +0 -3
  16. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.js +1 -2
  17. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +0 -2
  18. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +0 -1
  19. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.d.ts +2 -3
  20. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.js +5 -18
  21. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.d.ts +2 -1
  22. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.js +16 -0
  23. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +1 -1
  24. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +50 -27
  25. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +0 -1
  26. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +1 -1
  27. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +1 -1
  28. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +5 -9
  29. package/dist/conversion/hub/process/chat-process-continue-execution.js +3 -0
  30. package/dist/conversion/hub/process/chat-process-media.d.ts +2 -1
  31. package/dist/conversion/hub/process/chat-process-media.js +63 -9
  32. package/dist/conversion/hub/process/chat-process-session-usage.d.ts +6 -24
  33. package/dist/conversion/hub/process/chat-process-session-usage.js +101 -200
  34. package/dist/conversion/hub/response/provider-response.js +13 -13
  35. package/dist/conversion/hub/types/chat-envelope.d.ts +0 -1
  36. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +4 -0
  37. package/dist/conversion/responses/responses-openai-bridge.d.ts +0 -1
  38. package/dist/conversion/responses/responses-openai-bridge.js +34 -28
  39. package/dist/conversion/shared/anthropic-message-utils.js +1 -14
  40. package/dist/conversion/shared/reasoning-normalizer.js +22 -41
  41. package/dist/conversion/shared/responses-tool-utils.js +2 -3
  42. package/dist/conversion/shared/tool-governor.js +4 -2
  43. package/dist/native/router_hotpath_napi.node +0 -0
  44. package/dist/router/virtual-router/engine/routing-state/store.js +2 -21
  45. package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.d.ts +0 -1
  46. package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.js +0 -1
  47. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +0 -3
  48. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +0 -72
  49. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +1 -1
  50. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +1 -1
  51. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.d.ts +2 -2
  52. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.js +96 -80
  53. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.d.ts +1 -0
  54. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.js +29 -0
  55. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +2 -6
  56. package/dist/router/virtual-router/engine.js +6 -9
  57. package/dist/router/virtual-router/routing-instructions/state.js +27 -37
  58. package/dist/router/virtual-router/routing-instructions/types.d.ts +4 -6
  59. package/dist/router/virtual-router/token-estimator.js +0 -21
  60. package/dist/servertool/handlers/stop-message-auto.js +1 -11
  61. package/dist/tools/apply-patch/execution-capturer.d.ts +1 -1
  62. package/dist/tools/apply-patch/execution-capturer.js +2 -1
  63. package/dist/tools/apply-patch/regression-capturer.js +1 -2
  64. package/dist/tools/tool-registry.js +2 -1
  65. package/package.json +1 -1
@@ -1,28 +1,8 @@
1
1
  import { runRespInboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
2
- import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
3
- const PROFILE = 'chat:gemini-cli';
4
- const DEFAULT_PROVIDER_PROTOCOL = 'gemini-chat';
5
- const DEFAULT_ENTRY_ENDPOINT = '/v1/chat/completions';
6
- function buildGeminiCliResponseCompatInput(payload, adapterContext) {
7
- const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
8
- return {
9
- payload,
10
- adapterContext: {
11
- ...nativeContext,
12
- compatibilityProfile: PROFILE,
13
- providerProtocol: nativeContext.providerProtocol ??
14
- adapterContext?.providerProtocol ??
15
- DEFAULT_PROVIDER_PROTOCOL,
16
- entryEndpoint: nativeContext.entryEndpoint ??
17
- adapterContext?.entryEndpoint ??
18
- DEFAULT_ENTRY_ENDPOINT,
19
- },
20
- explicitProfile: PROFILE,
21
- };
22
- }
2
+ import { buildGeminiCliCompatInput } from './gemini-cli-request.js';
23
3
  export function cacheAntigravityThoughtSignatureFromGeminiResponse(payload, adapterContext) {
24
4
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
25
5
  return payload;
26
6
  }
27
- return runRespInboundStage3CompatWithNative(buildGeminiCliResponseCompatInput(payload, adapterContext)).payload;
7
+ return runRespInboundStage3CompatWithNative(buildGeminiCliCompatInput(payload, adapterContext)).payload;
28
8
  }
@@ -103,6 +103,13 @@ export function applyDeepSeekWebResponseTransform(payload, adapterContext, confi
103
103
  reason: 'payload is not an object'
104
104
  });
105
105
  }
106
+ // Fail fast if response is missing required shape (choices array)
107
+ if (!Array.isArray(payload.choices)) {
108
+ // Allow business error format (code + msg) to pass through for separate handling
109
+ if (!('code' in payload && 'msg' in payload)) {
110
+ emitCompatError(new Error('[deepseek-web] invalid response: missing required "choices" array'), adapterContext, payload, { reason: 'missing required response shape' });
111
+ }
112
+ }
106
113
  if (isNativeDisabledByEnv()) {
107
114
  emitCompatError(makeNativeRequiredError('runRespInboundStage3CompatJson', 'native disabled'), adapterContext, payload, { reason: 'native disabled' });
108
115
  }
@@ -1,4 +1,155 @@
1
- import { applyFieldMappingsWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
1
+ const MODEL_PREFIX_NORMALIZATION = {
2
+ 'gpt-': 'glm-'
3
+ };
4
+ const FINISH_REASON_MAP = {
5
+ tool_calls: 'tool_calls',
6
+ stop: 'stop',
7
+ length: 'length',
8
+ sensitive: 'content_filter',
9
+ network_error: 'error'
10
+ };
11
+ function isRecord(value) {
12
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
13
+ }
2
14
  export function applyFieldMappings(payload, mappings) {
3
- return applyFieldMappingsWithNative(payload, mappings);
15
+ const result = { ...payload };
16
+ for (const mapping of mappings) {
17
+ applySingleMapping(result, mapping);
18
+ }
19
+ return result;
20
+ }
21
+ function applySingleMapping(root, mapping) {
22
+ const sourceValue = getNestedProperty(root, mapping.sourcePath);
23
+ if (sourceValue === undefined) {
24
+ return;
25
+ }
26
+ const transformed = convertType(applyTransform(sourceValue, mapping.transform), mapping.type);
27
+ setNestedProperty(root, mapping.targetPath, transformed);
28
+ }
29
+ function applyTransform(value, transform) {
30
+ if (!transform) {
31
+ return value;
32
+ }
33
+ switch (transform) {
34
+ case 'timestamp':
35
+ return typeof value === 'number' ? value : Date.now();
36
+ case 'lowercase':
37
+ return typeof value === 'string' ? value.toLowerCase() : value;
38
+ case 'uppercase':
39
+ return typeof value === 'string' ? value.toUpperCase() : value;
40
+ case 'normalizeModelName':
41
+ if (typeof value === 'string') {
42
+ for (const [prefix, replacement] of Object.entries(MODEL_PREFIX_NORMALIZATION)) {
43
+ if (value.startsWith(prefix)) {
44
+ return value.replace(prefix, replacement);
45
+ }
46
+ }
47
+ }
48
+ return value;
49
+ case 'normalizeFinishReason':
50
+ if (typeof value === 'string') {
51
+ return FINISH_REASON_MAP[value] ?? value;
52
+ }
53
+ return value;
54
+ default:
55
+ return value;
56
+ }
57
+ }
58
+ function convertType(value, targetType) {
59
+ if (value === null || value === undefined) {
60
+ return value;
61
+ }
62
+ switch (targetType) {
63
+ case 'string':
64
+ return String(value);
65
+ case 'number': {
66
+ const num = Number(value);
67
+ return Number.isNaN(num) ? 0 : num;
68
+ }
69
+ case 'boolean':
70
+ return Boolean(value);
71
+ case 'object':
72
+ return isRecord(value) ? value : {};
73
+ case 'array':
74
+ return Array.isArray(value) ? value : [value];
75
+ default:
76
+ return value;
77
+ }
78
+ }
79
+ function getNestedProperty(obj, pathExpression) {
80
+ const keys = pathExpression.split('.');
81
+ if (pathExpression.includes('[*]')) {
82
+ return getWildcardProperty(obj, keys);
83
+ }
84
+ return keys.reduce((current, key) => {
85
+ if (!isRecord(current)) {
86
+ return undefined;
87
+ }
88
+ return current[key];
89
+ }, obj);
90
+ }
91
+ function getWildcardProperty(obj, keys) {
92
+ const results = [];
93
+ const processWildcard = (current, keyIndex) => {
94
+ if (keyIndex >= keys.length) {
95
+ results.push(current);
96
+ return;
97
+ }
98
+ const key = keys[keyIndex];
99
+ if (key === '[*]') {
100
+ if (Array.isArray(current)) {
101
+ current.forEach(item => processWildcard(item, keyIndex + 1));
102
+ }
103
+ return;
104
+ }
105
+ if (isRecord(current) && current[key] !== undefined) {
106
+ processWildcard(current[key], keyIndex + 1);
107
+ }
108
+ };
109
+ processWildcard(obj, 0);
110
+ return results;
111
+ }
112
+ function setNestedProperty(obj, pathExpression, value) {
113
+ const keys = pathExpression.split('.');
114
+ if (pathExpression.includes('[*]')) {
115
+ setWildcardProperty(obj, keys, value);
116
+ return;
117
+ }
118
+ const lastKey = keys.pop();
119
+ if (!lastKey) {
120
+ return;
121
+ }
122
+ const target = keys.reduce((current, key) => {
123
+ if (!isRecord(current[key])) {
124
+ current[key] = {};
125
+ }
126
+ return current[key];
127
+ }, obj);
128
+ target[lastKey] = value;
129
+ }
130
+ function setWildcardProperty(obj, keys, value) {
131
+ const processSetWildcard = (current, keyIndex) => {
132
+ if (keyIndex >= keys.length - 1) {
133
+ const lastKey = keys[keys.length - 1].replace('[*]', '');
134
+ if (Array.isArray(current)) {
135
+ current.forEach(item => {
136
+ if (isRecord(item)) {
137
+ item[lastKey] = value;
138
+ }
139
+ });
140
+ }
141
+ return;
142
+ }
143
+ const key = keys[keyIndex];
144
+ if (key === '[*]') {
145
+ if (Array.isArray(current)) {
146
+ current.forEach(item => processSetWildcard(item, keyIndex + 1));
147
+ }
148
+ return;
149
+ }
150
+ if (isRecord(current)) {
151
+ processSetWildcard(current[key], keyIndex + 1);
152
+ }
153
+ };
154
+ processSetWildcard(obj, 0);
4
155
  }
@@ -1,5 +1,95 @@
1
- import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
2
- import { stringifyLmstudioResponsesInputWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
1
+ function isRecord(value) {
2
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
3
+ }
4
+ function extractTextParts(content) {
5
+ const out = [];
6
+ if (typeof content === 'string' && content.trim().length) {
7
+ out.push(content.trim());
8
+ return out;
9
+ }
10
+ if (!Array.isArray(content)) {
11
+ return out;
12
+ }
13
+ for (const part of content) {
14
+ if (!isRecord(part))
15
+ continue;
16
+ const type = typeof part.type === 'string' ? String(part.type).trim().toLowerCase() : '';
17
+ const text = typeof part.text === 'string'
18
+ ? part.text
19
+ : typeof part.content === 'string'
20
+ ? String(part.content)
21
+ : undefined;
22
+ if (typeof text === 'string' && text.trim().length) {
23
+ out.push(text.trim());
24
+ continue;
25
+ }
26
+ // OpenAI Responses content parts often use { type: 'input_text'|'output_text', text: '...' }.
27
+ if ((type === 'input_text' || type === 'output_text') && typeof part.text === 'string') {
28
+ const t = String(part.text).trim();
29
+ if (t.length)
30
+ out.push(t);
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+ function stringifyInputItems(input) {
36
+ if (!Array.isArray(input))
37
+ return null;
38
+ const chunks = [];
39
+ for (const item of input) {
40
+ if (!isRecord(item))
41
+ continue;
42
+ const type = typeof item.type === 'string' ? String(item.type).trim().toLowerCase() : '';
43
+ const roleCandidate = typeof item.role === 'string' ? String(item.role).trim() : '';
44
+ const messageNode = isRecord(item.message) ? item.message : undefined;
45
+ const nestedRoleCandidate = messageNode && typeof messageNode.role === 'string' ? String(messageNode.role).trim() : '';
46
+ // OpenAI Responses supports message-like items without an explicit `type` field:
47
+ // { role: 'user'|'assistant'|'system', content: [...] }
48
+ if (type === 'message' || (!type && (roleCandidate || nestedRoleCandidate))) {
49
+ const role = roleCandidate ||
50
+ nestedRoleCandidate ||
51
+ 'user';
52
+ const contentNode = item.content !== undefined ? item.content : messageNode?.content;
53
+ const parts = extractTextParts(contentNode);
54
+ if (parts.length) {
55
+ chunks.push(`${role}: ${parts.join('\n')}`);
56
+ }
57
+ continue;
58
+ }
59
+ if (type === 'function_call') {
60
+ const name = typeof item.name === 'string' ? String(item.name).trim() : 'tool';
61
+ const args = typeof item.arguments === 'string'
62
+ ? String(item.arguments)
63
+ : (() => {
64
+ try {
65
+ return JSON.stringify(item.arguments ?? null);
66
+ }
67
+ catch {
68
+ return String(item.arguments ?? '');
69
+ }
70
+ })();
71
+ chunks.push(`assistant tool_call ${name}: ${args}`);
72
+ continue;
73
+ }
74
+ if (type === 'function_call_output') {
75
+ const output = typeof item.output === 'string'
76
+ ? String(item.output)
77
+ : (() => {
78
+ try {
79
+ return JSON.stringify(item.output ?? null);
80
+ }
81
+ catch {
82
+ return String(item.output ?? '');
83
+ }
84
+ })();
85
+ chunks.push(`tool_output: ${output}`);
86
+ continue;
87
+ }
88
+ }
89
+ if (!chunks.length)
90
+ return '';
91
+ return chunks.join('\n\n');
92
+ }
3
93
  /**
4
94
  * Legacy compatibility shim:
5
95
  * Some older LM Studio builds rejected the array form of `input` ("Invalid type for 'input'").
@@ -20,5 +110,16 @@ export function stringifyLmstudioResponsesInput(payload, adapterContext) {
20
110
  if (!adapterContext || adapterContext.providerProtocol !== 'openai-responses') {
21
111
  return payload;
22
112
  }
23
- return stringifyLmstudioResponsesInputWithNative(payload, buildNativeReqOutboundCompatAdapterContext(adapterContext));
113
+ const record = payload;
114
+ const input = record.input;
115
+ if (!Array.isArray(input)) {
116
+ return payload;
117
+ }
118
+ const flattened = stringifyInputItems(input);
119
+ if (flattened === null) {
120
+ return payload;
121
+ }
122
+ const instructions = typeof record.instructions === 'string' ? record.instructions.trim() : '';
123
+ record.input = instructions.length ? `${instructions}\n\n${flattened}`.trim() : flattened;
124
+ return payload;
24
125
  }
@@ -108,7 +108,7 @@ function deriveAdapterContext(context, fallbackProtocol) {
108
108
  (typeof metadata.providerProtocol === 'string' ? metadata.providerProtocol : undefined) ||
109
109
  fallbackProtocol;
110
110
  const streamingHint = metadata.stream === true ? 'force' : metadata.stream === false ? 'disable' : 'auto';
111
- const toolCallIdStyle = normalizeToolCallIdStyleCandidate(requestContext.toolCallIdStyle);
111
+ const toolCallIdStyle = normalizeToolCallIdStyleCandidate(metadata.toolCallIdStyle);
112
112
  return {
113
113
  requestId: context.request.id,
114
114
  entryEndpoint: (typeof requestContext.entryEndpoint === 'string' ? requestContext.entryEndpoint : context.request.endpoint) ||
@@ -29,8 +29,7 @@ const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
29
29
  'metadata',
30
30
  'stream',
31
31
  'tool_choice',
32
- 'thinking',
33
- 'disable_parallel_tool_use'
32
+ 'thinking'
34
33
  ]);
35
34
  const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
36
35
  const PASSTHROUGH_PARAMETERS = ['tool_choice'];
@@ -95,9 +94,6 @@ function collectParameters(payload) {
95
94
  params[key] = payload[key];
96
95
  }
97
96
  }
98
- if (typeof payload.disable_parallel_tool_use === 'boolean') {
99
- params.parallel_tool_calls = !payload.disable_parallel_tool_use;
100
- }
101
97
  if (Array.isArray(payload.stop_sequences)) {
102
98
  params.stop = payload.stop_sequences;
103
99
  }
@@ -388,10 +384,6 @@ export class AnthropicSemanticMapper {
388
384
  }
389
385
  if (trimmedParameters) {
390
386
  for (const [key, value] of Object.entries(trimmedParameters)) {
391
- if (key === 'parallel_tool_calls') {
392
- baseRequest.parallel_tool_calls = value;
393
- continue;
394
- }
395
387
  if (ANTHROPIC_TOP_LEVEL_FIELDS.has(key) || key === 'stop') {
396
388
  if (key === 'messages' || key === 'tools') {
397
389
  continue;
@@ -1,9 +1,8 @@
1
1
  import { isJsonObject, jsonClone } from '../../types/json.js';
2
2
  import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesRequestFromChat } from '../../../responses/responses-openai-bridge.js';
3
+ import { logHubStageTiming } from '../../pipeline/hub-stage-timing.js';
3
4
  import { maybeAugmentApplyPatchErrorContent } from './chat-mapper.js';
4
5
  import { mapReqInboundBridgeToolsToChatWithNative, mapReqInboundResumeToolOutputsDetailedWithNative } from '../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
5
- import { mapOpenaiChatToChatWithNative } from '../../../../router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.js';
6
- import { runResponsesOpenAIRequestCodecWithNative } from '../../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
7
6
  const RESPONSES_PARAMETER_KEYS = [
8
7
  'model',
9
8
  'temperature',
@@ -392,35 +391,22 @@ function buildSubmitToolOutputsPayload(chat, ctx, responsesContext) {
392
391
  }
393
392
  export class ResponsesSemanticMapper {
394
393
  async toChat(format, ctx) {
394
+ const requestId = typeof ctx.requestId === 'string' && ctx.requestId.trim().length ? ctx.requestId : 'unknown';
395
395
  const payload = format.payload || {};
396
- if (!isSubmitToolOutputsEndpoint(ctx)) {
397
- const codecResult = runResponsesOpenAIRequestCodecWithNative(payload, { requestId: ctx.requestId });
398
- const request = isJsonObject(codecResult.request)
399
- ? codecResult.request
400
- : undefined;
401
- const responsesContext = isJsonObject(codecResult.context)
402
- ? codecResult.context
403
- : undefined;
404
- if (request && responsesContext) {
405
- const chatEnvelope = mapOpenaiChatToChatWithNative(request, ctx);
406
- const missingFields = [];
407
- const toolOutputs = mapToolOutputs(payload.tool_outputs, missingFields);
408
- if (toolOutputs?.length) {
409
- chatEnvelope.toolOutputs = toolOutputs;
410
- }
411
- if (missingFields.length) {
412
- const metadata = chatEnvelope.metadata && typeof chatEnvelope.metadata === 'object'
413
- ? chatEnvelope.metadata
414
- : { context: ctx };
415
- metadata.missingFields = missingFields;
416
- chatEnvelope.metadata = metadata;
417
- }
418
- chatEnvelope.semantics = attachResponsesSemantics(chatEnvelope.semantics, responsesContext, undefined);
419
- return chatEnvelope;
420
- }
421
- }
396
+ logHubStageTiming(requestId, 'req_inbound.responses.capture_context', 'start');
397
+ const captureStart = Date.now();
422
398
  const responsesContext = captureResponsesContext(payload, { route: { requestId: ctx.requestId } });
399
+ logHubStageTiming(requestId, 'req_inbound.responses.capture_context', 'completed', {
400
+ elapsedMs: Date.now() - captureStart,
401
+ forceLog: true
402
+ });
403
+ logHubStageTiming(requestId, 'req_inbound.responses.build_chat_request', 'start');
404
+ const buildChatStart = Date.now();
423
405
  const { request, toolsNormalized } = buildChatRequestFromResponses(payload, responsesContext);
406
+ logHubStageTiming(requestId, 'req_inbound.responses.build_chat_request', 'completed', {
407
+ elapsedMs: Date.now() - buildChatStart,
408
+ forceLog: true
409
+ });
424
410
  const missingFields = [];
425
411
  const messages = normalizeMessages(request.messages, missingFields);
426
412
  let toolOutputs = mapToolOutputs(payload.tool_outputs, missingFields);
@@ -441,6 +427,7 @@ export class ResponsesSemanticMapper {
441
427
  };
442
428
  }
443
429
  async fromChat(chat, ctx) {
430
+ const requestId = typeof ctx.requestId === 'string' && ctx.requestId.trim().length ? ctx.requestId : 'unknown';
444
431
  const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
445
432
  const responsesContext = selectResponsesContextSnapshot(chat, envelopeMetadata);
446
433
  if (isSubmitToolOutputsEndpoint(ctx)) {
@@ -465,13 +452,19 @@ export class ResponsesSemanticMapper {
465
452
  messages: chat.messages,
466
453
  tools: chat.tools
467
454
  };
468
- const originalSystemMessages = (chat.messages || [])
469
- .filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
470
- .map(message => serializeSystemContent(message))
471
- .filter((content) => typeof content === 'string' && content.length > 0);
472
- responsesContext.originalSystemMessages = originalSystemMessages;
473
- const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext, {
474
- routeToolCallIdStyle: ctx.toolCallIdStyle
455
+ if (!Array.isArray(responsesContext.originalSystemMessages) || responsesContext.originalSystemMessages.length === 0) {
456
+ const originalSystemMessages = (chat.messages || [])
457
+ .filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
458
+ .map(message => serializeSystemContent(message))
459
+ .filter((content) => typeof content === 'string' && content.length > 0);
460
+ responsesContext.originalSystemMessages = originalSystemMessages;
461
+ }
462
+ logHubStageTiming(requestId, 'req_outbound.responses.build_request', 'start');
463
+ const buildRequestStart = Date.now();
464
+ const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
465
+ logHubStageTiming(requestId, 'req_outbound.responses.build_request', 'completed', {
466
+ elapsedMs: Date.now() - buildRequestStart,
467
+ forceLog: true
475
468
  });
476
469
  const responses = responsesResult.request;
477
470
  if (chat.parameters && chat.parameters.stream !== undefined) {