@jsonstudio/llms 0.6.3409 → 0.6.3539

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 (82) hide show
  1. package/dist/conversion/codecs/anthropic-openai-codec.d.ts +12 -3
  2. package/dist/conversion/codecs/anthropic-openai-codec.js +32 -92
  3. package/dist/conversion/codecs/gemini-openai-codec.d.ts +6 -5
  4. package/dist/conversion/codecs/gemini-openai-codec.js +48 -685
  5. package/dist/conversion/codecs/openai-openai-codec.d.ts +1 -1
  6. package/dist/conversion/codecs/openai-openai-codec.js +34 -100
  7. package/dist/conversion/codecs/responses-openai-codec.d.ts +1 -1
  8. package/dist/conversion/codecs/responses-openai-codec.js +47 -159
  9. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +2 -6
  10. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +29 -245
  11. package/dist/conversion/compat/actions/anthropic-claude-code-user-id.d.ts +3 -0
  12. package/dist/conversion/compat/actions/anthropic-claude-code-user-id.js +30 -0
  13. package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +21 -232
  14. package/dist/conversion/compat/actions/deepseek-web-request.js +41 -276
  15. package/dist/conversion/compat/actions/deepseek-web-response.js +64 -859
  16. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +1 -1
  17. package/dist/conversion/compat/actions/gemini-cli-request.js +20 -613
  18. package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -15
  19. package/dist/conversion/compat/actions/gemini-web-search.js +22 -69
  20. package/dist/conversion/compat/actions/glm-tool-extraction.d.ts +3 -2
  21. package/dist/conversion/compat/actions/glm-tool-extraction.js +28 -257
  22. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +0 -8
  23. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +24 -206
  24. package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -2
  25. package/dist/conversion/compat/actions/qwen-transform.js +30 -271
  26. package/dist/conversion/compat/actions/tool-text-request-guidance.js +3 -173
  27. package/dist/conversion/compat/actions/universal-shape-filter.d.ts +6 -23
  28. package/dist/conversion/compat/actions/universal-shape-filter.js +4 -383
  29. package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +1 -0
  30. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +1 -2
  31. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +50 -104
  32. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +12 -10
  33. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +0 -2
  34. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +46 -67
  35. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +15 -40
  36. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +47 -348
  37. package/dist/conversion/responses/responses-openai-bridge.js +129 -611
  38. package/dist/conversion/shared/chat-output-normalizer.js +6 -0
  39. package/dist/conversion/shared/chat-request-filters.js +1 -1
  40. package/dist/conversion/shared/output-content-normalizer.js +10 -0
  41. package/dist/conversion/shared/responses-conversation-store.js +22 -135
  42. package/dist/conversion/shared/responses-output-builder.d.ts +0 -2
  43. package/dist/conversion/shared/responses-output-builder.js +28 -318
  44. package/dist/conversion/shared/responses-response-utils.js +35 -86
  45. package/dist/conversion/shared/streaming-text-extractor.d.ts +1 -2
  46. package/dist/conversion/shared/streaming-text-extractor.js +13 -14
  47. package/dist/native/router_hotpath_napi.node +0 -0
  48. package/dist/router/virtual-router/bootstrap/routing-config.js +11 -3
  49. package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
  50. package/dist/router/virtual-router/engine-legacy.js +15 -7
  51. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
  52. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
  53. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
  54. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
  55. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
  56. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
  57. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
  58. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
  59. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
  60. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
  61. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
  62. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
  63. package/dist/router/virtual-router/engine.js +0 -38
  64. package/dist/router/virtual-router/features.js +44 -3
  65. package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
  66. package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
  67. package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
  68. package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
  69. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
  70. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
  71. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
  72. package/package.json +1 -1
  73. package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
  74. package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
  75. package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
  76. package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
  77. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
  78. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
  79. package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
  80. package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
  81. package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
  82. package/dist/router/virtual-router/engine-legacy/routing.js +0 -8
@@ -1,11 +1,10 @@
1
- import { ProtocolConversionPipeline } from '../../index.js';
2
- import { chatEnvelopeToStandardized } from '../../../hub/standardized-bridge.js';
3
- import { ResponsesFormatAdapter } from '../../../hub/format-adapters/responses-format-adapter.js';
4
- import { ResponsesSemanticMapper } from '../../../hub/semantic-mappers/responses-mapper.js';
5
- import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesPayloadFromChat } from '../../../responses/responses-openai-bridge.js';
6
- import { buildAdapterContextFromPipeline } from '../../hooks/adapter-context.js';
7
1
  import { runStandardChatRequestFilters } from '../../../index.js';
2
+ import { buildChatRequestFromResponses, buildResponsesPayloadFromChat, captureResponsesContext } from '../../../responses/responses-openai-bridge.js';
3
+ import { ConversionMetaBag } from '../../meta/meta-bag.js';
8
4
  import { canonicalizeOpenAIChatResponse, convertStandardizedToOpenAIChat as convertCanonicalToOpenAIChat, OPENAI_PROTOCOL } from './shared/openai-chat-helpers.js';
5
+ import { buildAdapterContextFromPipeline } from '../../hooks/adapter-context.js';
6
+ import { chatEnvelopeToStandardizedWithNative } from '../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
7
+ import { standardizedToChatEnvelopeWithNative } from '../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
9
8
  const DEFAULT_RESPONSES_ENDPOINT = '/v1/responses';
10
9
  const RESPONSES_PROTOCOL = 'openai-responses';
11
10
  function assertJsonObject(value, stage) {
@@ -117,49 +116,6 @@ function captureToolResults(payload) {
117
116
  }
118
117
  return results;
119
118
  }
120
- function createResponsesHooks() {
121
- const formatAdapter = new ResponsesFormatAdapter();
122
- const semanticMapper = new ResponsesSemanticMapper();
123
- return {
124
- id: 'responses-openai-v2',
125
- protocol: RESPONSES_PROTOCOL,
126
- inbound: {
127
- parse: async ({ wire, context, meta }) => {
128
- const adapterContext = buildAdapterContextFromPipeline(context, {
129
- defaultEntryEndpoint: DEFAULT_RESPONSES_ENDPOINT,
130
- overrideProtocol: RESPONSES_PROTOCOL
131
- });
132
- const formatEnvelope = await formatAdapter.parseRequest(wire, adapterContext);
133
- const chatEnvelope = await semanticMapper.toChat(formatEnvelope, adapterContext);
134
- const canonical = chatEnvelopeToStandardized(chatEnvelope, {
135
- adapterContext,
136
- endpoint: adapterContext.entryEndpoint,
137
- requestId: adapterContext.requestId
138
- });
139
- // Capture Responses-specific context for outbound mapping parity
140
- const responsesContext = captureResponsesContext(wire, { route: { requestId: adapterContext.requestId } });
141
- const built = buildChatRequestFromResponses(wire, responsesContext);
142
- if (built.toolsNormalized) {
143
- responsesContext.toolsNormalized = built.toolsNormalized;
144
- }
145
- const captured = captureToolResults(wire);
146
- if (captured.length) {
147
- responsesContext.__captured_tool_results = captured;
148
- }
149
- meta.set('responsesContext', cloneResponsesContext(responsesContext));
150
- return { canonical };
151
- }
152
- },
153
- outbound: {
154
- serialize: async ({ canonical, meta }) => {
155
- const stored = meta.consume('responsesContext');
156
- const responsesContext = restoreResponsesContext(stored);
157
- const payload = buildResponsesPayloadFromChat(canonical, responsesContext);
158
- return { payload: assertJsonObject(payload, 'responses_outbound_serialize') };
159
- }
160
- }
161
- };
162
- }
163
119
  function buildPipelineContext(profile, context) {
164
120
  return {
165
121
  requestId: context.requestId,
@@ -173,12 +129,8 @@ function buildPipelineContext(profile, context) {
173
129
  }
174
130
  export class ResponsesOpenAIPipelineCodec {
175
131
  id = 'responses-openai-v2';
176
- pipeline;
177
132
  requestMetaStore = new Map();
178
133
  initialized = false;
179
- constructor() {
180
- this.pipeline = new ProtocolConversionPipeline(createResponsesHooks());
181
- }
182
134
  async initialize() {
183
135
  this.initialized = true;
184
136
  }
@@ -195,13 +147,44 @@ export class ResponsesOpenAIPipelineCodec {
195
147
  const inboundContext = buildPipelineContext(profile, context);
196
148
  const requestId = context.requestId ?? inboundContext.requestId ?? `req_${Date.now()}`;
197
149
  inboundContext.requestId = requestId;
198
- const inboundOptions = {
199
- payload: assertJsonObject(payload, 'responses_inbound_request'),
200
- context: inboundContext
201
- };
202
- const inbound = await this.pipeline.convertInbound(inboundOptions);
203
- this.stashMeta(requestId, inbound.meta);
204
- const openaiPayload = await convertCanonicalToOpenAIChat(inbound.canonical, inbound.context);
150
+ const wire = assertJsonObject(payload, 'responses_inbound_request');
151
+ const responsesContext = captureResponsesContext(wire, { route: { requestId } });
152
+ const built = buildChatRequestFromResponses(wire, responsesContext);
153
+ if (built.toolsNormalized) {
154
+ responsesContext.toolsNormalized = built.toolsNormalized;
155
+ }
156
+ const captured = captureToolResults(wire);
157
+ if (captured.length) {
158
+ responsesContext.__captured_tool_results = captured;
159
+ }
160
+ const adapterContext = buildAdapterContextFromPipeline(inboundContext, {
161
+ defaultEntryEndpoint: DEFAULT_RESPONSES_ENDPOINT,
162
+ overrideProtocol: RESPONSES_PROTOCOL
163
+ });
164
+ const chatEnvelope = standardizedToChatEnvelopeWithNative({
165
+ request: {
166
+ model: built.request.model,
167
+ messages: built.request.messages,
168
+ tools: built.request.tools ?? [],
169
+ parameters: {
170
+ ...(typeof built.request.model === 'string'
171
+ ? { model: built.request.model }
172
+ : {})
173
+ },
174
+ metadata: {}
175
+ },
176
+ adapterContext: adapterContext
177
+ });
178
+ const canonical = chatEnvelopeToStandardizedWithNative({
179
+ chatEnvelope: chatEnvelope,
180
+ adapterContext: adapterContext,
181
+ endpoint: adapterContext.entryEndpoint ?? DEFAULT_RESPONSES_ENDPOINT,
182
+ requestId
183
+ });
184
+ const meta = new ConversionMetaBag();
185
+ meta.set('responsesContext', cloneResponsesContext(responsesContext));
186
+ this.stashMeta(requestId, meta);
187
+ const openaiPayload = await convertCanonicalToOpenAIChat(canonical, inboundContext);
205
188
  const filterContext = {
206
189
  ...context,
207
190
  requestId,
@@ -221,13 +204,9 @@ export class ResponsesOpenAIPipelineCodec {
221
204
  if (storedMeta) {
222
205
  this.requestMetaStore.delete(requestId);
223
206
  }
207
+ const stored = storedMeta?.responsesContext;
208
+ const responsesContext = restoreResponsesContext(stored);
224
209
  const sanitized = await canonicalizeOpenAIChatResponse(assertJsonObject(payload, 'responses_openai_response'), context);
225
- const outboundOptions = {
226
- canonical: sanitized,
227
- context: pipelineContext,
228
- meta: storedMeta
229
- };
230
- const outbound = await this.pipeline.convertOutbound(outboundOptions);
231
- return outbound.payload;
210
+ return assertJsonObject(buildResponsesPayloadFromChat(sanitized, responsesContext), 'responses_outbound_serialize');
232
211
  }
233
212
  }
@@ -1,13 +1,9 @@
1
- import { standardizedToChatEnvelope } from '../../../../hub/standardized-bridge.js';
2
1
  import { buildAdapterContextFromPipeline } from '../../../hooks/adapter-context.js';
3
- import { ChatFormatAdapter } from '../../../../hub/format-adapters/chat-format-adapter.js';
4
- import { ChatSemanticMapper } from '../../../../hub/semantic-mappers/chat-mapper.js';
5
- import { FilterEngine, ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } from '../../../../../filters/index.js';
6
- import { normalizeChatResponseReasoningTools } from '../../../../shared/reasoning-tool-normalizer.js';
2
+ import { standardizedToChatEnvelopeWithNative } from '../../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
3
+ import { mapOpenaiChatFromChatWithNative } from '../../../../../router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.js';
4
+ import { runOpenAIResponseCodecWithNative } from '../../../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
7
5
  export const DEFAULT_OPENAI_ENDPOINT = '/v1/chat/completions';
8
6
  export const OPENAI_PROTOCOL = 'openai-chat';
9
- const sharedChatFormatAdapter = new ChatFormatAdapter();
10
- const sharedChatSemanticMapper = new ChatSemanticMapper();
11
7
  function ensureJsonObject(value, stage) {
12
8
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
13
9
  throw new Error(`OpenAI chat helper requires JSON object payload at ${stage}`);
@@ -19,41 +15,20 @@ export async function convertStandardizedToOpenAIChat(standardized, context, opt
19
15
  defaultEntryEndpoint: options?.defaultEndpoint ?? DEFAULT_OPENAI_ENDPOINT,
20
16
  overrideProtocol: OPENAI_PROTOCOL
21
17
  });
22
- const chatEnvelope = standardizedToChatEnvelope(standardized, { adapterContext });
23
- const formatEnvelope = await sharedChatSemanticMapper.fromChat(chatEnvelope, adapterContext);
24
- const payload = await sharedChatFormatAdapter.buildRequest(formatEnvelope, adapterContext);
25
- return ensureJsonObject(payload, 'openai_request_build');
18
+ const chatEnvelope = standardizedToChatEnvelopeWithNative({
19
+ request: standardized,
20
+ adapterContext: adapterContext
21
+ });
22
+ const formatEnvelope = mapOpenaiChatFromChatWithNative(chatEnvelope, adapterContext);
23
+ return ensureJsonObject(formatEnvelope.payload, 'openai_request_build');
26
24
  }
27
25
  export async function canonicalizeOpenAIChatResponse(payload, context, options) {
28
- const dto = {
29
- data: payload,
30
- metadata: {
31
- requestId: context.requestId ?? `req_${Date.now()}`,
32
- pipelineId: context.metadata?.pipelineId ?? 'conversion-router',
33
- processingTime: 0,
34
- stages: []
35
- }
36
- };
37
- const filterContext = {
38
- requestId: dto.metadata.requestId,
39
- model: typeof payload.model === 'string' ? payload.model : undefined,
26
+ return ensureJsonObject(runOpenAIResponseCodecWithNative(payload, {
27
+ requestId: context.requestId ?? `req_${Date.now()}`,
40
28
  endpoint: context.entryEndpoint ?? context.endpoint ?? options?.defaultEndpoint ?? DEFAULT_OPENAI_ENDPOINT,
29
+ stream: context.stream === true,
30
+ reasoningMode: context.metadata?.reasoningMode,
41
31
  profile: options?.profile ?? OPENAI_PROTOCOL,
42
- debug: { emit: () => { } }
43
- };
44
- const engine = new FilterEngine();
45
- engine.registerFilter(new ResponseToolTextCanonicalizeFilter());
46
- engine.registerFilter(new ResponseToolArgumentsStringifyFilter());
47
- engine.registerFilter(new ResponseFinishInvariantsFilter());
48
- const stage1 = await engine.run('response_pre', dto.data, filterContext);
49
- const stage2 = await engine.run('response_map', stage1, filterContext);
50
- const stage3 = await engine.run('response_post', stage2, filterContext);
51
- const normalized = ensureJsonObject(stage3, 'openai_response_filters');
52
- try {
53
- normalizeChatResponseReasoningTools(normalized, { idPrefixBase: 'reasoning_choice' });
54
- }
55
- catch {
56
- // reasoning normalization best-effort
57
- }
58
- return normalized;
32
+ idPrefixBase: 'reasoning_choice'
33
+ }), 'openai_response_filters');
59
34
  }
@@ -2,270 +2,11 @@ import { evaluateResponsesHostPolicy } from '../responses-host-policy.js';
2
2
  import { normalizeMessageReasoningTools } from '../../shared/reasoning-tool-normalizer.js';
3
3
  import { createBridgeActionState, runBridgeActionPipeline } from '../../bridge-actions.js';
4
4
  import { resolveBridgePolicy, resolvePolicyActions } from '../../bridge-policies.js';
5
- import { buildResponsesOutputFromChat } from '../../shared/responses-output-builder.js';
6
5
  import { consumeResponsesPayloadSnapshot, consumeResponsesPassthrough } from '../../shared/responses-reasoning-registry.js';
7
- import { sanitizeResponsesFunctionName, stripInternalToolingMetadata } from '../../shared/responses-tool-utils.js';
6
+ import { stripInternalToolingMetadata } from '../../shared/responses-tool-utils.js';
8
7
  import { ProviderProtocolError } from '../../provider-protocol-error.js';
9
- import { normalizeResponsesToolCallArgumentsForClientWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
8
+ import { buildResponsesPayloadFromChatWithNative, normalizeResponsesToolCallArgumentsForClientWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
10
9
  import { normalizeChatResponseReasoningToolsWithNative } from '../../../router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js';
11
- function isPlainObject(value) {
12
- return Boolean(value && typeof value === 'object' && !Array.isArray(value));
13
- }
14
- function deepCloneRecord(value) {
15
- try {
16
- const structuredCloneImpl = globalThis.structuredClone;
17
- if (typeof structuredCloneImpl === 'function') {
18
- return structuredCloneImpl(value);
19
- }
20
- }
21
- catch {
22
- // ignore structuredClone failures
23
- }
24
- try {
25
- return JSON.parse(JSON.stringify(value));
26
- }
27
- catch {
28
- return { ...value };
29
- }
30
- }
31
- function isMissingResponseField(value) {
32
- if (value === undefined || value === null) {
33
- return true;
34
- }
35
- if (Array.isArray(value)) {
36
- return value.length === 0;
37
- }
38
- return false;
39
- }
40
- function mergeResponseOutputItems(baseOutput, sourceOutput) {
41
- if (!Array.isArray(baseOutput) || !Array.isArray(sourceOutput)) {
42
- return baseOutput;
43
- }
44
- const sourceById = new Map();
45
- sourceOutput.forEach((entry) => {
46
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
47
- return;
48
- }
49
- const id = typeof entry.id === 'string' ? entry.id.trim() : '';
50
- if (!id.length) {
51
- return;
52
- }
53
- sourceById.set(id, entry);
54
- });
55
- return baseOutput.map((entry, index) => {
56
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
57
- return entry;
58
- }
59
- const baseItem = deepCloneRecord(entry);
60
- const baseId = typeof baseItem.id === 'string' ? String(baseItem.id).trim() : '';
61
- let sourceItem;
62
- if (baseId.length) {
63
- sourceItem = sourceById.get(baseId);
64
- }
65
- if (!sourceItem) {
66
- const candidate = sourceOutput[index];
67
- if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) {
68
- sourceItem = candidate;
69
- }
70
- }
71
- if (!sourceItem) {
72
- return baseItem;
73
- }
74
- if (isMissingResponseField(baseItem.content) && sourceItem.content !== undefined) {
75
- baseItem.content = deepCloneRecord({ value: sourceItem.content }).value;
76
- }
77
- if (isMissingResponseField(baseItem.summary) && sourceItem.summary !== undefined) {
78
- baseItem.summary = deepCloneRecord({ value: sourceItem.summary }).value;
79
- }
80
- if (isMissingResponseField(baseItem.encrypted_content) && sourceItem.encrypted_content !== undefined) {
81
- baseItem.encrypted_content = deepCloneRecord({ value: sourceItem.encrypted_content }).value;
82
- }
83
- return baseItem;
84
- });
85
- }
86
- function mergeResponseTopLevelFields(options) {
87
- const merged = deepCloneRecord(options.base);
88
- const source = options.source;
89
- const sourceWins = options.sourceWinsKeys ?? [];
90
- for (const key of sourceWins) {
91
- if (source[key] !== undefined) {
92
- merged[key] = deepCloneRecord({ value: source[key] }).value;
93
- }
94
- }
95
- const passthroughKeys = ['metadata', 'temperature', 'top_p', 'prompt_cache_key', 'reasoning'];
96
- for (const key of passthroughKeys) {
97
- if (source[key] !== undefined && merged[key] === undefined) {
98
- merged[key] = deepCloneRecord({ value: source[key] }).value;
99
- }
100
- }
101
- if (merged.output !== undefined && source.output !== undefined) {
102
- merged.output = mergeResponseOutputItems(merged.output, source.output);
103
- }
104
- return merged;
105
- }
106
- function tryParseJson(value) {
107
- if (typeof value !== 'string')
108
- return null;
109
- const trimmed = value.trim();
110
- if (!trimmed)
111
- return null;
112
- if (!(trimmed.startsWith('{') || trimmed.startsWith('[')))
113
- return null;
114
- try {
115
- return JSON.parse(trimmed);
116
- }
117
- catch {
118
- return null;
119
- }
120
- }
121
- function looksLikeApplyPatchText(value) {
122
- const trimmed = value.trim();
123
- if (!trimmed)
124
- return false;
125
- return trimmed.includes('*** Begin Patch') && trimmed.includes('*** End Patch');
126
- }
127
- function extractFreeformTextFromArgs(argsRaw) {
128
- if (typeof argsRaw === 'string') {
129
- const trimmed = argsRaw.trim();
130
- if (!trimmed)
131
- return null;
132
- // If JSON wrapper, extract common fields first.
133
- const parsed = tryParseJson(trimmed);
134
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
135
- const rec = parsed;
136
- for (const key of ['instructions', 'patch', 'input', 'text']) {
137
- if (typeof rec[key] === 'string' && rec[key].trim().length) {
138
- return String(rec[key]).trim();
139
- }
140
- }
141
- }
142
- // If it's already freeform (not a JSON wrapper), keep.
143
- if (!trimmed.startsWith('{') && !trimmed.startsWith('[') && looksLikeApplyPatchText(trimmed)) {
144
- return trimmed;
145
- }
146
- // Unknown freeform: return as-is so client can error.
147
- return trimmed;
148
- }
149
- // If arguments is an object (malformed), try to extract patch-like fields.
150
- if (argsRaw && typeof argsRaw === 'object' && !Array.isArray(argsRaw)) {
151
- const rec = argsRaw;
152
- for (const key of ['instructions', 'patch', 'input', 'text']) {
153
- if (typeof rec[key] === 'string' && rec[key].trim().length) {
154
- return String(rec[key]).trim();
155
- }
156
- }
157
- }
158
- return null;
159
- }
160
- function extractJsonSchemaLike(parameters) {
161
- const required = Array.isArray(parameters.required)
162
- ? parameters.required.filter((v) => typeof v === 'string' && v.trim().length > 0).map((v) => v.trim())
163
- : [];
164
- const props = parameters.properties && typeof parameters.properties === 'object' && !Array.isArray(parameters.properties)
165
- ? parameters.properties
166
- : {};
167
- const properties = Object.keys(props);
168
- if (!required.length && !properties.length) {
169
- return null;
170
- }
171
- const additionalProperties = typeof parameters.additionalProperties === 'boolean' ? parameters.additionalProperties : true;
172
- return { required, properties, additionalProperties };
173
- }
174
- function repairToolArgsBySchemaKeys(toolName, record, schema) {
175
- // Apply known alias mappings only if the target keys are present in schema properties.
176
- const wants = new Set(schema.properties);
177
- const out = { ...record };
178
- if (toolName === 'exec_command') {
179
- if (wants.has('cmd') && out.command !== undefined && out.cmd === undefined)
180
- out.cmd = out.command;
181
- if (wants.has('command') && out.cmd !== undefined && out.command === undefined)
182
- out.command = out.cmd;
183
- }
184
- if (toolName === 'write_stdin') {
185
- if (wants.has('chars') && out.text !== undefined && out.chars === undefined)
186
- out.chars = out.text;
187
- if (wants.has('text') && out.chars !== undefined && out.text === undefined)
188
- out.text = out.chars;
189
- }
190
- if (toolName === 'apply_patch') {
191
- if (wants.has('instructions') && out.instructions === undefined) {
192
- if (typeof out.patch === 'string' && out.patch.trim().length)
193
- out.instructions = out.patch;
194
- else if (typeof out.input === 'string' && out.input.trim().length)
195
- out.instructions = out.input;
196
- }
197
- if (wants.has('patch') && out.patch === undefined) {
198
- if (typeof out.instructions === 'string' && out.instructions.trim().length)
199
- out.patch = out.instructions;
200
- else if (typeof out.input === 'string' && out.input.trim().length)
201
- out.patch = out.input;
202
- }
203
- }
204
- for (const key of schema.required) {
205
- if (!Object.prototype.hasOwnProperty.call(out, key)) {
206
- return null;
207
- }
208
- }
209
- if (schema.additionalProperties === false && schema.properties.length > 0) {
210
- for (const key of Object.keys(out)) {
211
- if (!wants.has(key))
212
- delete out[key];
213
- }
214
- }
215
- return out;
216
- }
217
- function buildClientToolIndex(toolsRaw) {
218
- const index = new Map();
219
- for (const tool of toolsRaw) {
220
- if (!tool || typeof tool !== 'object' || Array.isArray(tool))
221
- continue;
222
- const t = tool;
223
- const fn = t.function && typeof t.function === 'object' && !Array.isArray(t.function) ? t.function : undefined;
224
- const nameRaw = (fn && typeof fn.name === 'string' ? fn.name : undefined) ?? (typeof t.name === 'string' ? t.name : undefined);
225
- const name = typeof nameRaw === 'string' ? nameRaw.trim() : '';
226
- if (!name)
227
- continue;
228
- const formatRaw = (typeof t.format === 'string' ? t.format : undefined) ??
229
- (fn && typeof fn.format === 'string' ? fn.format : undefined);
230
- const parametersRaw = (fn && fn.parameters && typeof fn.parameters === 'object' && !Array.isArray(fn.parameters) ? fn.parameters : undefined) ??
231
- (t.parameters && typeof t.parameters === 'object' && !Array.isArray(t.parameters) ? t.parameters : undefined) ??
232
- undefined;
233
- index.set(name, {
234
- name,
235
- ...(typeof formatRaw === 'string' && formatRaw.trim().length ? { format: formatRaw.trim() } : {}),
236
- ...(parametersRaw ? { parameters: parametersRaw } : {})
237
- });
238
- }
239
- return index;
240
- }
241
- function resolveClientToolName(toolIndex, rawToolName) {
242
- const trimmed = rawToolName.trim();
243
- if (!trimmed.length)
244
- return undefined;
245
- if (toolIndex.has(trimmed))
246
- return trimmed;
247
- const lower = trimmed.toLowerCase();
248
- for (const key of toolIndex.keys()) {
249
- if (key.toLowerCase() === lower) {
250
- return key;
251
- }
252
- }
253
- const hasBash = Array.from(toolIndex.keys()).some((key) => key.toLowerCase() === 'bash');
254
- if (hasBash)
255
- return undefined;
256
- if (!['shell_command', 'exec_command', 'shell', 'terminal', 'bash'].includes(lower)) {
257
- return undefined;
258
- }
259
- const preferred = ['shell_command', 'exec_command', 'shell', 'terminal'];
260
- for (const candidate of preferred) {
261
- for (const key of toolIndex.keys()) {
262
- if (key.toLowerCase() === candidate) {
263
- return key;
264
- }
265
- }
266
- }
267
- return undefined;
268
- }
269
10
  function normalizeResponsesToolCallArgumentsForClient(responsesPayload, context) {
270
11
  const toolsRaw = Array.isArray(context?.toolsRaw) ? context?.toolsRaw : [];
271
12
  if (!toolsRaw.length) {
@@ -310,6 +51,23 @@ function shouldStripHostManagedFields(context) {
310
51
  const result = evaluateResponsesHostPolicy(context, typeof context?.targetProtocol === 'string' ? context?.targetProtocol : undefined);
311
52
  return result.shouldStripHostManagedFields;
312
53
  }
54
+ function collectRetentionContext(context) {
55
+ const stripHostManagedFields = shouldStripHostManagedFields(context);
56
+ return {
57
+ metadata: context?.metadata,
58
+ parallelToolCalls: context?.parallel_tool_calls,
59
+ toolChoice: context?.tool_choice,
60
+ include: context?.include,
61
+ store: context?.store,
62
+ stripHostManagedFields
63
+ };
64
+ }
65
+ function readInlineRetentionPayload(response, key) {
66
+ const candidate = response[key];
67
+ return candidate && typeof candidate === 'object' && !Array.isArray(candidate)
68
+ ? candidate
69
+ : undefined;
70
+ }
313
71
  export function buildResponsesPayloadFromChat(payload, context) {
314
72
  if (!payload || typeof payload !== 'object')
315
73
  return payload;
@@ -323,7 +81,10 @@ export function buildResponsesPayloadFromChat(payload, context) {
323
81
  const snapshotPayload = snapshotLookupKey ? consumeResponsesPayloadSnapshot(snapshotLookupKey) : undefined;
324
82
  const passthroughPayload = snapshotLookupKey ? consumeResponsesPassthrough(snapshotLookupKey) : undefined;
325
83
  const sourceForRetention = (passthroughPayload && typeof passthroughPayload === 'object' ? passthroughPayload : undefined) ??
326
- (snapshotPayload && typeof snapshotPayload === 'object' ? snapshotPayload : undefined);
84
+ readInlineRetentionPayload(response, '__responses_passthrough') ??
85
+ (snapshotPayload && typeof snapshotPayload === 'object' ? snapshotPayload : undefined) ??
86
+ readInlineRetentionPayload(response, '__responses_payload_snapshot');
87
+ const retentionContext = collectRetentionContext(context);
327
88
  const hasChoicesArray = Array.isArray(response.choices);
328
89
  const choicesLength = hasChoicesArray ? response.choices.length : 0;
329
90
  if (!hasChoicesArray || choicesLength === 0) {
@@ -338,47 +99,17 @@ export function buildResponsesPayloadFromChat(payload, context) {
338
99
  : typeof response.message === 'string' && response.message.trim().length
339
100
  ? response.message.trim()
340
101
  : 'Upstream returned non-standard Chat completion payload (missing choices).';
341
- const out = {
342
- id: response.id || `resp-${Date.now()}`,
343
- object: 'response',
344
- created_at: response.created_at || response.created || Math.floor(Date.now() / 1000),
345
- model: response.model,
346
- status: 'failed',
347
- output: []
348
- };
349
- if (message) {
350
- out.output_text = message;
351
- out.error = {
352
- type: 'provider_error',
353
- code: statusCode,
354
- message
355
- };
356
- }
357
- if (context) {
358
- for (const k of ['metadata', 'parallel_tool_calls', 'tool_choice', 'include']) {
359
- if (context[k] !== undefined)
360
- out[k] = context[k];
361
- }
362
- if (!shouldStripHostManagedFields(context) && context.store !== undefined) {
363
- out.store = context.store;
364
- }
365
- }
366
- if (typeof response.request_id === 'string') {
367
- out.request_id = response.request_id;
368
- }
369
- else if (typeof response.id === 'string') {
370
- out.request_id = response.id;
371
- }
372
- else if (typeof context?.requestId === 'string') {
373
- out.request_id = context.requestId;
374
- }
375
- const mergedFallback = sourceForRetention && typeof sourceForRetention === 'object'
376
- ? mergeResponseTopLevelFields({
377
- base: out,
378
- source: sourceForRetention,
379
- sourceWinsKeys: ['metadata', 'temperature', 'top_p', 'prompt_cache_key', 'reasoning']
380
- })
381
- : out;
102
+ const mergedFallback = buildResponsesPayloadFromChatWithNative(response, {
103
+ requestId: context?.requestId,
104
+ toolsRaw: Array.isArray(context?.toolsRaw) ? context?.toolsRaw : [],
105
+ metadata: retentionContext.metadata,
106
+ parallelToolCalls: retentionContext.parallelToolCalls,
107
+ toolChoice: retentionContext.toolChoice,
108
+ include: retentionContext.include,
109
+ store: retentionContext.store,
110
+ stripHostManagedFields: retentionContext.stripHostManagedFields,
111
+ sourceForRetention: sourceForRetention
112
+ });
382
113
  if (mergedFallback.metadata) {
383
114
  stripInternalToolingMetadata(mergedFallback.metadata);
384
115
  }
@@ -441,57 +172,25 @@ export function buildResponsesPayloadFromChat(payload, context) {
441
172
  // best-effort reasoning normalization
442
173
  }
443
174
  }
444
- const outputBuild = buildResponsesOutputFromChat({
445
- response: response,
446
- message,
175
+ const nativeBuilt = buildResponsesPayloadFromChatWithNative(response, {
447
176
  requestId: context?.requestId,
448
- sanitizeFunctionName: sanitizeResponsesFunctionName
177
+ toolsRaw: Array.isArray(context?.toolsRaw) ? context?.toolsRaw : [],
178
+ metadata: retentionContext.metadata,
179
+ parallelToolCalls: retentionContext.parallelToolCalls,
180
+ toolChoice: retentionContext.toolChoice,
181
+ include: retentionContext.include,
182
+ store: retentionContext.store,
183
+ stripHostManagedFields: retentionContext.stripHostManagedFields,
184
+ sourceForRetention: sourceForRetention
449
185
  });
450
186
  const out = {
451
- id: response.id || `resp-${Date.now()}`,
452
- object: 'response',
453
- created_at: response.created_at || response.created || Math.floor(Date.now() / 1000),
454
- model: response.model,
455
- status: outputBuild.status,
456
- output: outputBuild.outputItems
187
+ ...nativeBuilt
457
188
  };
458
- if (typeof outputBuild.outputText === 'string') {
459
- out.output_text = outputBuild.outputText;
460
- }
461
- if (outputBuild.usage !== undefined)
462
- out.usage = outputBuild.usage;
463
- if (outputBuild.requiredAction)
464
- out.required_action = outputBuild.requiredAction;
465
189
  normalizeResponsesToolCallArgumentsForClient(out, context);
466
- if (context) {
467
- for (const k of ['metadata', 'parallel_tool_calls', 'tool_choice', 'include']) {
468
- if (context[k] !== undefined)
469
- out[k] = context[k];
470
- }
471
- if (!shouldStripHostManagedFields(context) && context.store !== undefined) {
472
- out.store = context.store;
473
- }
190
+ if (out.metadata) {
191
+ stripInternalToolingMetadata(out.metadata);
474
192
  }
475
- if (typeof response.request_id === 'string') {
476
- out.request_id = response.request_id;
477
- }
478
- else if (typeof response.id === 'string') {
479
- out.request_id = response.id;
480
- }
481
- else if (typeof context?.requestId === 'string') {
482
- out.request_id = context.requestId;
483
- }
484
- const merged = sourceForRetention && typeof sourceForRetention === 'object'
485
- ? mergeResponseTopLevelFields({
486
- base: out,
487
- source: sourceForRetention,
488
- sourceWinsKeys: ['metadata', 'temperature', 'top_p', 'prompt_cache_key', 'reasoning']
489
- })
490
- : out;
491
- if (merged.metadata) {
492
- stripInternalToolingMetadata(merged.metadata);
493
- }
494
- return merged;
193
+ return out;
495
194
  }
496
195
  export function extractRequestIdFromResponse(response) {
497
196
  if (response && typeof response === 'object' && 'metadata' in response && response.metadata && typeof response.metadata === 'object') {