@jsonstudio/llms 0.6.147 → 0.6.198

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/codecs/gemini-openai-codec.js +15 -1
  2. package/dist/conversion/compat/actions/auto-thinking.d.ts +6 -0
  3. package/dist/conversion/compat/actions/auto-thinking.js +25 -0
  4. package/dist/conversion/compat/actions/field-mapping.d.ts +14 -0
  5. package/dist/conversion/compat/actions/field-mapping.js +155 -0
  6. package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -0
  7. package/dist/conversion/compat/actions/qwen-transform.js +209 -0
  8. package/dist/conversion/compat/actions/request-rules.d.ts +24 -0
  9. package/dist/conversion/compat/actions/request-rules.js +63 -0
  10. package/dist/conversion/compat/actions/response-blacklist.d.ts +14 -0
  11. package/dist/conversion/compat/actions/response-blacklist.js +85 -0
  12. package/dist/conversion/compat/actions/response-normalize.d.ts +5 -0
  13. package/dist/conversion/compat/actions/response-normalize.js +121 -0
  14. package/dist/conversion/compat/actions/response-validate.d.ts +5 -0
  15. package/dist/conversion/compat/actions/response-validate.js +76 -0
  16. package/dist/conversion/compat/actions/snapshot.d.ts +8 -0
  17. package/dist/conversion/compat/actions/snapshot.js +21 -0
  18. package/dist/conversion/compat/actions/tool-schema.d.ts +6 -0
  19. package/dist/conversion/compat/actions/tool-schema.js +91 -0
  20. package/dist/conversion/compat/actions/universal-shape-filter.d.ts +74 -0
  21. package/dist/conversion/compat/actions/universal-shape-filter.js +382 -0
  22. package/dist/conversion/compat/profiles/chat-glm.json +187 -13
  23. package/dist/conversion/compat/profiles/chat-iflow.json +194 -26
  24. package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -35
  25. package/dist/conversion/compat/profiles/chat-qwen.json +20 -16
  26. package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  27. package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
  28. package/dist/conversion/hub/pipeline/compat/compat-engine.js +429 -5
  29. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +47 -0
  30. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
  31. package/dist/conversion/hub/pipeline/hub-pipeline.js +35 -1
  32. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  33. package/dist/conversion/hub/pipeline/target-utils.js +3 -0
  34. package/dist/conversion/hub/response/response-runtime.js +23 -15
  35. package/dist/conversion/responses/responses-host-policy.d.ts +6 -0
  36. package/dist/conversion/responses/responses-host-policy.js +14 -0
  37. package/dist/conversion/responses/responses-openai-bridge.js +51 -2
  38. package/dist/conversion/shared/anthropic-message-utils.js +6 -0
  39. package/dist/conversion/shared/bridge-actions.js +1 -1
  40. package/dist/conversion/shared/bridge-policies.js +0 -1
  41. package/dist/conversion/shared/responses-conversation-store.js +3 -26
  42. package/dist/conversion/shared/responses-reasoning-registry.d.ts +4 -0
  43. package/dist/conversion/shared/responses-reasoning-registry.js +62 -1
  44. package/dist/conversion/shared/responses-response-utils.js +23 -1
  45. package/dist/conversion/shared/tool-canonicalizer.d.ts +2 -0
  46. package/dist/conversion/shared/tool-filter-pipeline.js +11 -0
  47. package/dist/router/virtual-router/bootstrap.js +239 -39
  48. package/dist/router/virtual-router/classifier.js +19 -51
  49. package/dist/router/virtual-router/context-advisor.d.ts +21 -0
  50. package/dist/router/virtual-router/context-advisor.js +76 -0
  51. package/dist/router/virtual-router/engine.d.ts +11 -27
  52. package/dist/router/virtual-router/engine.js +191 -396
  53. package/dist/router/virtual-router/features.js +24 -607
  54. package/dist/router/virtual-router/health-manager.js +2 -7
  55. package/dist/router/virtual-router/message-utils.d.ts +7 -0
  56. package/dist/router/virtual-router/message-utils.js +66 -0
  57. package/dist/router/virtual-router/provider-registry.js +6 -2
  58. package/dist/router/virtual-router/token-estimator.d.ts +2 -0
  59. package/dist/router/virtual-router/token-estimator.js +16 -0
  60. package/dist/router/virtual-router/token-file-scanner.d.ts +15 -0
  61. package/dist/router/virtual-router/token-file-scanner.js +56 -0
  62. package/dist/router/virtual-router/tool-signals.d.ts +13 -0
  63. package/dist/router/virtual-router/tool-signals.js +403 -0
  64. package/dist/router/virtual-router/types.d.ts +21 -7
  65. package/dist/router/virtual-router/types.js +1 -0
  66. package/package.json +2 -2
@@ -3,7 +3,7 @@ import { deriveToolCallKey } from '../../shared/tool-call-utils.js';
3
3
  import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
4
4
  import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
5
5
  import { normalizeAnthropicToolName } from '../../shared/anthropic-message-utils.js';
6
- import { registerResponsesReasoning, consumeResponsesReasoning, registerResponsesOutputTextMeta, consumeResponsesOutputTextMeta } from '../../shared/responses-reasoning-registry.js';
6
+ import { registerResponsesReasoning, consumeResponsesReasoning, registerResponsesOutputTextMeta, consumeResponsesOutputTextMeta, consumeResponsesPayloadSnapshot, registerResponsesPayloadSnapshot, consumeResponsesPassthrough, registerResponsesPassthrough } from '../../shared/responses-reasoning-registry.js';
7
7
  function flattenAnthropicContent(content) {
8
8
  if (typeof content === 'string')
9
9
  return content;
@@ -262,6 +262,20 @@ export function buildOpenAIChatFromAnthropicMessage(payload, options) {
262
262
  if (preservedOutputMeta) {
263
263
  chatResponse.__responses_output_text_meta = preservedOutputMeta;
264
264
  }
265
+ const payloadSnapshot = consumeResponsesPayloadSnapshot(chatResponse.id);
266
+ if (payloadSnapshot) {
267
+ registerResponsesPayloadSnapshot(chatResponse.id, payloadSnapshot);
268
+ if (typeof chatResponse.request_id !== 'string') {
269
+ chatResponse.request_id = chatResponse.id;
270
+ }
271
+ }
272
+ const passthroughPayload = consumeResponsesPassthrough(chatResponse.id);
273
+ if (passthroughPayload) {
274
+ registerResponsesPassthrough(chatResponse.id, passthroughPayload);
275
+ if (typeof chatResponse.request_id !== 'string') {
276
+ chatResponse.request_id = chatResponse.id;
277
+ }
278
+ }
265
279
  if (Object.keys(aliasCollector).length && !chatResponse.anthropicToolNameMap) {
266
280
  chatResponse.anthropicToolNameMap = aliasCollector;
267
281
  }
@@ -358,27 +372,21 @@ export function buildAnthropicResponseFromChat(chatResponse, options) {
358
372
  default: return 'end_turn';
359
373
  }
360
374
  })();
361
- const promptTokens = usage && typeof usage === 'object'
362
- ? Number(usage.prompt_tokens ?? usage.input_tokens ?? 0)
363
- : 0;
364
- const completionTokens = usage && typeof usage === 'object'
365
- ? Number(usage.completion_tokens ?? usage.output_tokens ?? 0)
366
- : 0;
367
- const usagePayload = (promptTokens || completionTokens)
368
- ? {
369
- input_tokens: promptTokens,
370
- output_tokens: completionTokens
371
- }
372
- : undefined;
375
+ const canonicalId = typeof chatResponse.request_id === 'string'
376
+ ? chatResponse.request_id
377
+ : (typeof chatResponse.id === 'string' ? chatResponse.id : `resp_${Date.now()}`);
373
378
  const raw = {
374
- id: typeof chatResponse.id === 'string' ? chatResponse.id : `resp_${Date.now()}`,
379
+ id: canonicalId,
375
380
  type: 'message',
376
381
  role: 'assistant',
377
382
  content: contentBlocks,
378
383
  model: typeof chatResponse.model === 'string' ? chatResponse.model : 'unknown',
379
384
  stop_reason: stopReasonMapped,
380
385
  usage: usage && typeof usage === 'object'
381
- ? usagePayload
386
+ ? {
387
+ input_tokens: usage.prompt_tokens ?? 0,
388
+ output_tokens: usage.completion_tokens ?? 0
389
+ }
382
390
  : undefined
383
391
  };
384
392
  const sanitized = sanitizeAnthropicMessage(raw);
@@ -0,0 +1,6 @@
1
+ import type { ResponsesRequestContext } from './responses-openai-bridge.js';
2
+ export interface ResponsesHostPolicyResult {
3
+ shouldStripHostManagedFields: boolean;
4
+ targetProtocol: string;
5
+ }
6
+ export declare function evaluateResponsesHostPolicy(context?: ResponsesRequestContext, targetProtocol?: string): ResponsesHostPolicyResult;
@@ -0,0 +1,14 @@
1
+ export function evaluateResponsesHostPolicy(context, targetProtocol) {
2
+ const protocol = typeof targetProtocol === 'string' && targetProtocol.trim()
3
+ ? targetProtocol
4
+ : (typeof context?.targetProtocol === 'string' && context.targetProtocol.trim()
5
+ ? context.targetProtocol
6
+ : 'responses');
7
+ const normalized = protocol.toLowerCase();
8
+ const shouldStrip = normalized !== 'openai-responses' &&
9
+ normalized !== 'responses';
10
+ return {
11
+ shouldStripHostManagedFields: shouldStrip,
12
+ targetProtocol: normalized
13
+ };
14
+ }
@@ -1,4 +1,5 @@
1
1
  import { ensureBridgeInstructions } from '../shared/bridge-instructions.js';
2
+ import { evaluateResponsesHostPolicy } from './responses-host-policy.js';
2
3
  import { convertMessagesToBridgeInput, convertBridgeInputToChatMessages } from '../shared/bridge-message-utils.js';
3
4
  import { createToolCallIdTransformer, enforceToolCallIdStyle, resolveToolCallIdStyle, stripInternalToolingMetadata, sanitizeResponsesFunctionName } from '../shared/responses-tool-utils.js';
4
5
  import { mapBridgeToolsToChat, mapChatToolsToBridge } from '../shared/tool-mapping.js';
@@ -8,6 +9,7 @@ import { normalizeMessageReasoningTools } from '../shared/reasoning-tool-normali
8
9
  import { createBridgeActionState, runBridgeActionPipeline } from '../shared/bridge-actions.js';
9
10
  import { resolveBridgePolicy, resolvePolicyActions } from '../shared/bridge-policies.js';
10
11
  import { buildResponsesOutputFromChat } from '../shared/responses-output-builder.js';
12
+ import { consumeResponsesPayloadSnapshot, consumeResponsesPassthrough } from '../shared/responses-reasoning-registry.js';
11
13
  function isObject(v) {
12
14
  return !!v && typeof v === 'object' && !Array.isArray(v);
13
15
  }
@@ -257,12 +259,20 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
257
259
  else if (metadataExtraFields?.include !== undefined && out.include === undefined) {
258
260
  out.include = metadataExtraFields.include;
259
261
  }
260
- if (ctx?.store !== undefined && out.store === undefined) {
262
+ const stripHostFields = shouldStripHostManagedFields(ctx);
263
+ if (stripHostFields) {
264
+ delete out.store;
265
+ }
266
+ else if (ctx?.store !== undefined && out.store === undefined) {
261
267
  out.store = ctx.store;
262
268
  }
263
269
  else if (metadataExtraFields?.store !== undefined && out.store === undefined) {
264
270
  out.store = metadataExtraFields.store;
265
271
  }
272
+ else if (out.store === undefined) {
273
+ // Chat 入口无 store 概念,但 Responses provider 仍要求显式声明。
274
+ out.store = false;
275
+ }
266
276
  if (ctx?.toolChoice !== undefined && out.tool_choice === undefined) {
267
277
  out.tool_choice = ctx.toolChoice;
268
278
  }
@@ -433,6 +443,17 @@ export function buildResponsesPayloadFromChat(payload, context) {
433
443
  const response = unwrapData(payload);
434
444
  if (!response || typeof response !== 'object')
435
445
  return payload;
446
+ const snapshotKey = resolveSnapshotLookupKey(response, context);
447
+ if (snapshotKey) {
448
+ const passthrough = consumeResponsesPassthrough(snapshotKey);
449
+ if (passthrough) {
450
+ return passthrough;
451
+ }
452
+ const snapshot = consumeResponsesPayloadSnapshot(snapshotKey);
453
+ if (snapshot) {
454
+ return snapshot;
455
+ }
456
+ }
436
457
  if (response.object === 'response' && Array.isArray(response.output)) {
437
458
  return response;
438
459
  }
@@ -499,10 +520,22 @@ export function buildResponsesPayloadFromChat(payload, context) {
499
520
  out.required_action = outputBuild.requiredAction;
500
521
  // Do not inject captured tool results here; keep Chat back-half behavior standard.
501
522
  if (context) {
502
- for (const k of ['metadata', 'parallel_tool_calls', 'tool_choice', 'include', 'store']) {
523
+ for (const k of ['metadata', 'parallel_tool_calls', 'tool_choice', 'include']) {
503
524
  if (context[k] !== undefined)
504
525
  out[k] = context[k];
505
526
  }
527
+ if (!shouldStripHostManagedFields(context) && context.store !== undefined) {
528
+ out.store = context.store;
529
+ }
530
+ }
531
+ if (typeof response.request_id === 'string') {
532
+ out.request_id = response.request_id;
533
+ }
534
+ else if (typeof response.id === 'string') {
535
+ out.request_id = response.id;
536
+ }
537
+ else if (typeof context?.requestId === 'string') {
538
+ out.request_id = context.requestId;
506
539
  }
507
540
  if (out.metadata) {
508
541
  stripInternalToolingMetadata(out.metadata);
@@ -524,6 +557,22 @@ function unwrapData(value) {
524
557
  }
525
558
  return current;
526
559
  }
560
+ function resolveSnapshotLookupKey(response, context) {
561
+ if (typeof response?.request_id === 'string') {
562
+ return response.request_id;
563
+ }
564
+ if (typeof context?.requestId === 'string') {
565
+ return context.requestId;
566
+ }
567
+ if (typeof response?.id === 'string') {
568
+ return response.id;
569
+ }
570
+ return undefined;
571
+ }
572
+ function shouldStripHostManagedFields(context) {
573
+ const result = evaluateResponsesHostPolicy(context, typeof context?.targetProtocol === 'string' ? context?.targetProtocol : undefined);
574
+ return result.shouldStripHostManagedFields;
575
+ }
527
576
  export function extractRequestIdFromResponse(response) {
528
577
  if (response && typeof response === 'object' && 'metadata' in response && response.metadata && typeof response.metadata === 'object') {
529
578
  const meta = response.metadata;
@@ -336,6 +336,12 @@ export function buildOpenAIChatFromAnthropic(payload) {
336
336
  request.top_p = body.top_p;
337
337
  if (typeof body.stream === 'boolean')
338
338
  request.stream = body.stream;
339
+ if (typeof body.id === 'string') {
340
+ request.request_id = body.id;
341
+ }
342
+ else if (typeof body.request_id === 'string') {
343
+ request.request_id = body.request_id;
344
+ }
339
345
  if ('tool_choice' in body)
340
346
  request.tool_choice = body.tool_choice;
341
347
  const normalizedTools = mapAnthropicToolsToChat(body.tools);
@@ -540,7 +540,7 @@ const captureToolResultsAction = (ctx) => {
540
540
  name: typeof entry?.name === 'string' ? entry.name : undefined
541
541
  }));
542
542
  }
543
- if ((ctx.stage === 'request_outbound' || ctx.stage === 'response_outbound') &&
543
+ if (ctx.stage === 'request_outbound' &&
544
544
  Array.isArray(ctx.state.capturedToolResults) &&
545
545
  ctx.state.capturedToolResults.length) {
546
546
  const metadata = ensureMetadataRecord(ctx.state);
@@ -232,7 +232,6 @@ const ANTHROPIC_POLICY = {
232
232
  ],
233
233
  outbound: [
234
234
  reasoningAction('anthropic_reasoning'),
235
- { name: 'tools.capture-results' },
236
235
  toolCallNormalizationAction('anthropic_tool_call'),
237
236
  { name: 'metadata.extra-fields', options: { allowedKeys: ANTHROPIC_ALLOWED_FIELDS } }
238
237
  ]
@@ -205,29 +205,16 @@ class ResponsesConversationStore {
205
205
  }
206
206
  resumeConversation(responseId, submitPayload, options) {
207
207
  if (typeof responseId !== 'string' || !responseId.trim()) {
208
- raiseResumeError('Responses conversation requires valid response_id', {
209
- code: 'RESPONSES_RESUME_MISSING_ID',
210
- status: 422,
211
- origin: 'client'
212
- });
208
+ throw new Error('Responses conversation requires valid response_id');
213
209
  }
214
210
  this.prune();
215
211
  const entry = this.responseIndex.get(responseId);
216
212
  if (!entry) {
217
- raiseResumeError('Responses conversation expired or not found', {
218
- code: 'RESPONSES_RESUME_NOT_FOUND',
219
- status: 500,
220
- origin: 'server',
221
- details: { responseId }
222
- });
213
+ throw new Error('Responses conversation expired or not found');
223
214
  }
224
215
  const toolOutputs = Array.isArray(submitPayload.tool_outputs) ? submitPayload.tool_outputs : [];
225
216
  if (!toolOutputs.length) {
226
- raiseResumeError('tool_outputs array is required when submitting Responses tool results', {
227
- code: 'RESPONSES_RESUME_MISSING_OUTPUTS',
228
- status: 422,
229
- origin: 'client'
230
- });
217
+ throw new Error('tool_outputs array is required when submitting Responses tool results');
231
218
  }
232
219
  const mergedInput = coerceInputArray(entry.input);
233
220
  const normalizedOutputs = normalizeSubmittedToolOutputs(toolOutputs);
@@ -294,16 +281,6 @@ class ResponsesConversationStore {
294
281
  }
295
282
  const store = new ResponsesConversationStore();
296
283
  const RESPONSES_DEBUG = (process.env.ROUTECODEX_RESPONSES_DEBUG || '').trim() === '1';
297
- function raiseResumeError(message, options) {
298
- const err = new Error(message);
299
- err.code = options?.code ?? 'RESPONSES_RESUME_ERROR';
300
- err.status = options?.status;
301
- err.origin = options?.origin;
302
- if (options?.details) {
303
- err.details = options.details;
304
- }
305
- throw err;
306
- }
307
284
  export function captureResponsesRequestContext(args) {
308
285
  try {
309
286
  if (RESPONSES_DEBUG) {
@@ -6,3 +6,7 @@ export declare function registerResponsesReasoning(id: unknown, segments: string
6
6
  export declare function consumeResponsesReasoning(id: unknown): string[] | undefined;
7
7
  export declare function registerResponsesOutputTextMeta(id: unknown, meta: ResponsesOutputTextMeta | undefined): void;
8
8
  export declare function consumeResponsesOutputTextMeta(id: unknown): ResponsesOutputTextMeta | undefined;
9
+ export declare function registerResponsesPayloadSnapshot(id: unknown, snapshot: Record<string, unknown> | undefined): void;
10
+ export declare function consumeResponsesPayloadSnapshot(id: unknown): Record<string, unknown> | undefined;
11
+ export declare function registerResponsesPassthrough(id: unknown, payload: Record<string, unknown> | undefined): void;
12
+ export declare function consumeResponsesPassthrough(id: unknown): Record<string, unknown> | undefined;
@@ -11,10 +11,27 @@ function pruneEntry(id) {
11
11
  const entry = registry.get(id);
12
12
  if (!entry)
13
13
  return;
14
- if (!entry.reasoning && !entry.outputText) {
14
+ if (!entry.reasoning && !entry.outputText && !entry.payloadSnapshot && !entry.passthroughPayload) {
15
15
  registry.delete(id);
16
16
  }
17
17
  }
18
+ function cloneSnapshot(snapshot) {
19
+ try {
20
+ const structuredCloneImpl = globalThis.structuredClone;
21
+ if (typeof structuredCloneImpl === 'function') {
22
+ return structuredCloneImpl(snapshot);
23
+ }
24
+ }
25
+ catch {
26
+ /* ignore structuredClone failures */
27
+ }
28
+ try {
29
+ return JSON.parse(JSON.stringify(snapshot));
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
18
35
  export function registerResponsesReasoning(id, segments) {
19
36
  if (typeof id !== 'string')
20
37
  return;
@@ -59,3 +76,47 @@ export function consumeResponsesOutputTextMeta(id) {
59
76
  pruneEntry(id);
60
77
  return value;
61
78
  }
79
+ export function registerResponsesPayloadSnapshot(id, snapshot) {
80
+ if (typeof id !== 'string')
81
+ return;
82
+ if (!snapshot || typeof snapshot !== 'object')
83
+ return;
84
+ const clone = cloneSnapshot(snapshot);
85
+ if (!clone)
86
+ return;
87
+ const entry = ensureEntry(id);
88
+ entry.payloadSnapshot = clone;
89
+ }
90
+ export function consumeResponsesPayloadSnapshot(id) {
91
+ if (typeof id !== 'string')
92
+ return undefined;
93
+ const entry = registry.get(id);
94
+ if (!entry?.payloadSnapshot)
95
+ return undefined;
96
+ const clone = cloneSnapshot(entry.payloadSnapshot) ?? entry.payloadSnapshot;
97
+ entry.payloadSnapshot = undefined;
98
+ pruneEntry(id);
99
+ return clone;
100
+ }
101
+ export function registerResponsesPassthrough(id, payload) {
102
+ if (typeof id !== 'string')
103
+ return;
104
+ if (!payload || typeof payload !== 'object')
105
+ return;
106
+ const clone = cloneSnapshot(payload);
107
+ if (!clone)
108
+ return;
109
+ const entry = ensureEntry(id);
110
+ entry.passthroughPayload = clone;
111
+ }
112
+ export function consumeResponsesPassthrough(id) {
113
+ if (typeof id !== 'string')
114
+ return undefined;
115
+ const entry = registry.get(id);
116
+ if (!entry?.passthroughPayload)
117
+ return undefined;
118
+ const clone = cloneSnapshot(entry.passthroughPayload) ?? entry.passthroughPayload;
119
+ entry.passthroughPayload = undefined;
120
+ pruneEntry(id);
121
+ return clone;
122
+ }
@@ -4,6 +4,7 @@ import { extractOutputSegments } from './output-content-normalizer.js';
4
4
  import { sanitizeReasoningTaggedText } from './reasoning-utils.js';
5
5
  import { createBridgeActionState, runBridgeActionPipeline } from './bridge-actions.js';
6
6
  import { resolveBridgePolicy, resolvePolicyActions } from './bridge-policies.js';
7
+ import { registerResponsesPayloadSnapshot, registerResponsesPassthrough } from './responses-reasoning-registry.js';
7
8
  function selectCallId(entry) {
8
9
  const candidates = [
9
10
  entry?.call_id,
@@ -153,13 +154,27 @@ function collectRawReasoningSegments(response) {
153
154
  }
154
155
  return segments;
155
156
  }
157
+ function registerPassthroughSnapshot(payload) {
158
+ const ids = new Set();
159
+ const requestId = typeof payload?.request_id === 'string' ? payload.request_id.trim() : '';
160
+ const id = typeof payload?.id === 'string' ? payload.id.trim() : '';
161
+ if (requestId.length)
162
+ ids.add(requestId);
163
+ if (id.length)
164
+ ids.add(id);
165
+ for (const candidate of ids) {
166
+ registerResponsesPassthrough(candidate, payload);
167
+ }
168
+ }
156
169
  export function buildChatResponseFromResponses(payload) {
157
170
  if (!payload || typeof payload !== 'object')
158
171
  return payload;
159
172
  const response = unwrapResponsesResponse(payload);
160
173
  if (!response) {
161
- if (Array.isArray(payload.choices))
174
+ if (Array.isArray(payload.choices)) {
175
+ registerPassthroughSnapshot(payload);
162
176
  return payload;
177
+ }
163
178
  return payload;
164
179
  }
165
180
  const id = typeof response.id === 'string' ? response.id : `resp_${Date.now()}`;
@@ -235,5 +250,12 @@ export function buildChatResponseFromResponses(payload) {
235
250
  if (usage !== undefined) {
236
251
  chat.usage = usage;
237
252
  }
253
+ const requestId = typeof response.request_id === 'string'
254
+ ? response.request_id
255
+ : (typeof response.id === 'string' ? response.id : undefined);
256
+ if (requestId) {
257
+ chat.request_id = requestId;
258
+ }
259
+ registerResponsesPayloadSnapshot(id, response);
238
260
  return chat;
239
261
  }
@@ -0,0 +1,2 @@
1
+ export declare function canonicalizeChatResponseTools(payload: unknown): unknown;
2
+ export default canonicalizeChatResponseTools;
@@ -1,5 +1,6 @@
1
1
  import { FilterEngine } from '../../filters/index.js';
2
2
  import { loadFieldMapConfig } from '../../filters/utils/fieldmap-loader.js';
3
+ import { normalizeChatResponseReasoningTools } from './reasoning-tool-normalizer.js';
3
4
  import { createSnapshotWriter } from './snapshot-utils.js';
4
5
  const REQUEST_FILTER_STAGES = [
5
6
  'request_pre',
@@ -191,6 +192,16 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
191
192
  snapshot(stage, payload);
192
193
  };
193
194
  recordStage('resp_process_tool_filters_input', chatJson);
195
+ try {
196
+ if (chatJson && typeof chatJson === 'object') {
197
+ normalizeChatResponseReasoningTools(chatJson, {
198
+ idPrefixBase: 'reasoning_choice'
199
+ });
200
+ }
201
+ }
202
+ catch {
203
+ // best-effort; do not block response flow on reasoning parsing issues
204
+ }
194
205
  const engine = new FilterEngine();
195
206
  const registeredStages = new Set();
196
207
  const register = (filter) => {