@jsonstudio/llms 0.4.4 → 0.4.6

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 (160) hide show
  1. package/dist/conversion/codec-registry.js +11 -1
  2. package/dist/conversion/codecs/anthropic-openai-codec.d.ts +13 -0
  3. package/dist/conversion/codecs/anthropic-openai-codec.js +18 -473
  4. package/dist/conversion/codecs/gemini-openai-codec.js +91 -48
  5. package/dist/conversion/codecs/responses-openai-codec.js +9 -2
  6. package/dist/conversion/hub/format-adapters/anthropic-format-adapter.js +3 -0
  7. package/dist/conversion/hub/format-adapters/chat-format-adapter.js +3 -0
  8. package/dist/conversion/hub/format-adapters/gemini-format-adapter.js +3 -0
  9. package/dist/conversion/hub/format-adapters/responses-format-adapter.d.ts +19 -0
  10. package/dist/conversion/hub/format-adapters/responses-format-adapter.js +9 -0
  11. package/dist/conversion/hub/node-support.js +3 -1
  12. package/dist/conversion/hub/pipeline/hub-pipeline.js +37 -32
  13. package/dist/conversion/hub/response/provider-response.js +1 -1
  14. package/dist/conversion/hub/response/response-mappers.js +1 -1
  15. package/dist/conversion/hub/response/response-runtime.js +109 -10
  16. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +70 -156
  17. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +63 -52
  18. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +76 -143
  19. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +40 -160
  20. package/dist/conversion/hub/standardized-bridge.js +3 -0
  21. package/dist/conversion/hub/tool-governance/rules.js +2 -2
  22. package/dist/conversion/index.d.ts +5 -0
  23. package/dist/conversion/index.js +5 -0
  24. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +12 -0
  25. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +100 -0
  26. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.d.ts +15 -0
  27. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +174 -0
  28. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +14 -0
  29. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +166 -0
  30. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.d.ts +13 -0
  31. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +66 -0
  32. package/dist/conversion/pipeline/hooks/adapter-context.d.ts +7 -0
  33. package/dist/conversion/pipeline/hooks/adapter-context.js +18 -0
  34. package/dist/conversion/pipeline/hooks/protocol-hooks.d.ts +67 -0
  35. package/dist/conversion/pipeline/hooks/protocol-hooks.js +1 -0
  36. package/dist/conversion/pipeline/index.d.ts +35 -0
  37. package/dist/conversion/pipeline/index.js +103 -0
  38. package/dist/conversion/pipeline/meta/meta-bag.d.ts +20 -0
  39. package/dist/conversion/pipeline/meta/meta-bag.js +81 -0
  40. package/dist/conversion/pipeline/schema/canonical-chat.d.ts +18 -0
  41. package/dist/conversion/pipeline/schema/canonical-chat.js +1 -0
  42. package/dist/conversion/pipeline/schema/index.d.ts +1 -0
  43. package/dist/conversion/pipeline/schema/index.js +1 -0
  44. package/dist/conversion/responses/responses-openai-bridge.d.ts +48 -0
  45. package/dist/conversion/responses/responses-openai-bridge.js +157 -1146
  46. package/dist/conversion/shared/anthropic-message-utils.d.ts +12 -0
  47. package/dist/conversion/shared/anthropic-message-utils.js +587 -0
  48. package/dist/conversion/shared/bridge-actions.d.ts +39 -0
  49. package/dist/conversion/shared/bridge-actions.js +709 -0
  50. package/dist/conversion/shared/bridge-conversation-store.d.ts +41 -0
  51. package/dist/conversion/shared/bridge-conversation-store.js +279 -0
  52. package/dist/conversion/shared/bridge-id-utils.d.ts +7 -0
  53. package/dist/conversion/shared/bridge-id-utils.js +42 -0
  54. package/dist/conversion/shared/bridge-instructions.d.ts +1 -0
  55. package/dist/conversion/shared/bridge-instructions.js +113 -0
  56. package/dist/conversion/shared/bridge-message-types.d.ts +39 -0
  57. package/dist/conversion/shared/bridge-message-types.js +1 -0
  58. package/dist/conversion/shared/bridge-message-utils.d.ts +22 -0
  59. package/dist/conversion/shared/bridge-message-utils.js +473 -0
  60. package/dist/conversion/shared/bridge-metadata.d.ts +1 -0
  61. package/dist/conversion/shared/bridge-metadata.js +1 -0
  62. package/dist/conversion/shared/bridge-policies.d.ts +18 -0
  63. package/dist/conversion/shared/bridge-policies.js +276 -0
  64. package/dist/conversion/shared/bridge-request-adapter.d.ts +28 -0
  65. package/dist/conversion/shared/bridge-request-adapter.js +430 -0
  66. package/dist/conversion/shared/chat-output-normalizer.d.ts +4 -0
  67. package/dist/conversion/shared/chat-output-normalizer.js +56 -0
  68. package/dist/conversion/shared/chat-request-filters.js +24 -1
  69. package/dist/conversion/shared/gemini-tool-utils.d.ts +5 -0
  70. package/dist/conversion/shared/gemini-tool-utils.js +130 -0
  71. package/dist/conversion/shared/metadata-passthrough.d.ts +11 -0
  72. package/dist/conversion/shared/metadata-passthrough.js +57 -0
  73. package/dist/conversion/shared/output-content-normalizer.d.ts +12 -0
  74. package/dist/conversion/shared/output-content-normalizer.js +119 -0
  75. package/dist/conversion/shared/reasoning-normalizer.d.ts +21 -0
  76. package/dist/conversion/shared/reasoning-normalizer.js +368 -0
  77. package/dist/conversion/shared/reasoning-tool-normalizer.d.ts +12 -0
  78. package/dist/conversion/shared/reasoning-tool-normalizer.js +132 -0
  79. package/dist/conversion/shared/reasoning-tool-parser.d.ts +10 -0
  80. package/dist/conversion/shared/reasoning-tool-parser.js +95 -0
  81. package/dist/conversion/shared/reasoning-utils.d.ts +2 -0
  82. package/dist/conversion/shared/reasoning-utils.js +42 -0
  83. package/dist/conversion/shared/responses-conversation-store.js +5 -11
  84. package/dist/conversion/shared/responses-message-utils.d.ts +15 -0
  85. package/dist/conversion/shared/responses-message-utils.js +206 -0
  86. package/dist/conversion/shared/responses-output-builder.d.ts +15 -0
  87. package/dist/conversion/shared/responses-output-builder.js +179 -0
  88. package/dist/conversion/shared/responses-output-utils.d.ts +7 -0
  89. package/dist/conversion/shared/responses-output-utils.js +108 -0
  90. package/dist/conversion/shared/responses-request-adapter.d.ts +28 -0
  91. package/dist/conversion/shared/responses-request-adapter.js +9 -40
  92. package/dist/conversion/shared/responses-response-utils.d.ts +3 -0
  93. package/dist/conversion/shared/responses-response-utils.js +209 -0
  94. package/dist/conversion/shared/responses-tool-utils.d.ts +12 -0
  95. package/dist/conversion/shared/responses-tool-utils.js +90 -0
  96. package/dist/conversion/shared/responses-types.d.ts +33 -0
  97. package/dist/conversion/shared/responses-types.js +1 -0
  98. package/dist/conversion/shared/tool-call-utils.d.ts +11 -0
  99. package/dist/conversion/shared/tool-call-utils.js +56 -0
  100. package/dist/conversion/shared/tool-governor.js +5 -0
  101. package/dist/conversion/shared/tool-mapping.d.ts +19 -0
  102. package/dist/conversion/shared/tool-mapping.js +124 -0
  103. package/dist/conversion/shared/tool-normalizers.d.ts +4 -0
  104. package/dist/conversion/shared/tool-normalizers.js +84 -0
  105. package/dist/router/virtual-router/bootstrap.js +18 -3
  106. package/dist/router/virtual-router/provider-registry.js +4 -2
  107. package/dist/router/virtual-router/types.d.ts +212 -0
  108. package/dist/sse/index.d.ts +38 -2
  109. package/dist/sse/index.js +27 -0
  110. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +14 -0
  111. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.js +106 -73
  112. package/dist/sse/json-to-sse/chat-json-to-sse-converter.js +6 -2
  113. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +14 -0
  114. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.js +99 -0
  115. package/dist/sse/json-to-sse/index.d.ts +7 -0
  116. package/dist/sse/json-to-sse/index.js +2 -0
  117. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +13 -0
  118. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.js +150 -0
  119. package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +39 -0
  120. package/dist/sse/json-to-sse/sequencers/chat-sequencer.js +49 -3
  121. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +10 -0
  122. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.js +95 -0
  123. package/dist/sse/json-to-sse/sequencers/responses-sequencer.js +31 -5
  124. package/dist/sse/registry/sse-codec-registry.d.ts +32 -0
  125. package/dist/sse/registry/sse-codec-registry.js +30 -1
  126. package/dist/sse/shared/reasoning-dispatcher.d.ts +10 -0
  127. package/dist/sse/shared/reasoning-dispatcher.js +25 -0
  128. package/dist/sse/shared/responses-output-normalizer.d.ts +12 -0
  129. package/dist/sse/shared/responses-output-normalizer.js +45 -0
  130. package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +2 -0
  131. package/dist/sse/shared/serializers/anthropic-event-serializer.js +9 -0
  132. package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +2 -0
  133. package/dist/sse/shared/serializers/gemini-event-serializer.js +5 -0
  134. package/dist/sse/shared/serializers/index.d.ts +41 -0
  135. package/dist/sse/shared/serializers/index.js +2 -0
  136. package/dist/sse/shared/writer.d.ts +127 -0
  137. package/dist/sse/shared/writer.js +37 -1
  138. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +11 -0
  139. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +92 -127
  140. package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +16 -0
  141. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +151 -0
  142. package/dist/sse/sse-to-json/builders/response-builder.d.ts +165 -0
  143. package/dist/sse/sse-to-json/builders/response-builder.js +27 -6
  144. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +114 -0
  145. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +79 -3
  146. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +13 -0
  147. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +160 -0
  148. package/dist/sse/sse-to-json/index.d.ts +7 -0
  149. package/dist/sse/sse-to-json/index.js +2 -0
  150. package/dist/sse/sse-to-json/parsers/sse-parser.js +53 -1
  151. package/dist/sse/types/anthropic-types.d.ts +170 -0
  152. package/dist/sse/types/anthropic-types.js +8 -5
  153. package/dist/sse/types/chat-types.d.ts +10 -0
  154. package/dist/sse/types/chat-types.js +2 -1
  155. package/dist/sse/types/core-interfaces.d.ts +1 -1
  156. package/dist/sse/types/gemini-types.d.ts +116 -0
  157. package/dist/sse/types/gemini-types.js +5 -0
  158. package/dist/sse/types/index.d.ts +5 -2
  159. package/dist/sse/types/index.js +2 -0
  160. package/package.json +1 -1
@@ -0,0 +1,179 @@
1
+ import { normalizeFunctionCallId } from './bridge-id-utils.js';
2
+ import { normalizeContentPart } from './output-content-normalizer.js';
3
+ import { expandResponsesMessageItem } from '../../sse/shared/responses-output-normalizer.js';
4
+ export function buildResponsesOutputFromChat(options) {
5
+ const { response, message, requestId, sanitizeFunctionName } = options;
6
+ const outputItems = [];
7
+ const allocateOutputId = (prefix) => `${prefix}_${requestId ?? Date.now()}_${outputItems.length + 1}`;
8
+ const role = message?.role || 'assistant';
9
+ const content = message?.content;
10
+ const reasoningText = typeof message?.reasoning_content === 'string' && message.reasoning_content.trim().length
11
+ ? String(message.reasoning_content).trim()
12
+ : undefined;
13
+ const convertedContent = convertChatContentToResponses(content);
14
+ if (message || convertedContent.length > 0 || reasoningText) {
15
+ const responsesMessage = {
16
+ id: allocateOutputId('message'),
17
+ type: 'message',
18
+ status: 'completed',
19
+ role,
20
+ content: convertedContent.length > 0 ? convertedContent : []
21
+ };
22
+ const expandedItems = expandResponsesMessageItem(responsesMessage, {
23
+ requestId: requestId ?? 'responses_outbound',
24
+ outputIndex: outputItems.length,
25
+ extraReasoning: reasoningText
26
+ });
27
+ for (const expanded of expandedItems) {
28
+ outputItems.push(expanded);
29
+ }
30
+ }
31
+ let toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
32
+ const normalizedToolCalls = [];
33
+ try {
34
+ toolCalls = toolCalls.filter((it) => {
35
+ const nm = (it && typeof it === 'object') ? (it?.function?.name || it.name) : undefined;
36
+ return typeof nm === 'string' && nm.trim().length > 0 && nm.toLowerCase() !== 'tool';
37
+ });
38
+ }
39
+ catch {
40
+ /* ignore */
41
+ }
42
+ let toolFallbackCounter = 0;
43
+ for (const call of toolCalls) {
44
+ const outputEntry = buildFunctionCallOutput(call, allocateOutputId, sanitizeFunctionName, outputItems.length, ++toolFallbackCounter);
45
+ if (outputEntry) {
46
+ outputItems.push(outputEntry.output);
47
+ normalizedToolCalls.push({
48
+ id: outputEntry.call_id,
49
+ name: outputEntry.name,
50
+ args: outputEntry.arguments
51
+ });
52
+ }
53
+ }
54
+ const usage = normalizeUsage(response.usage);
55
+ const outputText = extractOutputText(convertedContent);
56
+ const hasToolCalls = toolCalls.length > 0;
57
+ if (hasToolCalls) {
58
+ for (const item of outputItems) {
59
+ if (item.type === 'message') {
60
+ item.status = 'in_progress';
61
+ }
62
+ }
63
+ }
64
+ const requiredAction = normalizedToolCalls.length
65
+ ? buildRequiredActionFromNormalized(normalizedToolCalls)
66
+ : undefined;
67
+ const status = hasToolCalls ? 'requires_action' : 'completed';
68
+ return {
69
+ outputItems,
70
+ outputText,
71
+ status,
72
+ requiredAction,
73
+ usage
74
+ };
75
+ }
76
+ function normalizeUsage(usageRaw) {
77
+ if (usageRaw && typeof usageRaw === 'object') {
78
+ const usage = { ...usageRaw };
79
+ if (usage.input_tokens != null && usage.prompt_tokens == null) {
80
+ usage.prompt_tokens = usage.input_tokens;
81
+ }
82
+ if (usage.output_tokens != null && usage.completion_tokens == null) {
83
+ usage.completion_tokens = usage.output_tokens;
84
+ }
85
+ if (usage.prompt_tokens != null && usage.completion_tokens != null && usage.total_tokens == null) {
86
+ const total = Number(usage.prompt_tokens) + Number(usage.completion_tokens);
87
+ if (!Number.isNaN(total))
88
+ usage.total_tokens = total;
89
+ }
90
+ try {
91
+ delete usage.input_tokens;
92
+ delete usage.output_tokens;
93
+ }
94
+ catch {
95
+ /* ignore */
96
+ }
97
+ return usage;
98
+ }
99
+ return usageRaw;
100
+ }
101
+ function buildFunctionCallOutput(call, allocateOutputId, sanitizeFunctionName, baseCount, offset) {
102
+ try {
103
+ const fn = call?.function || {};
104
+ const callName = typeof fn?.name === 'string' && fn.name.trim().length
105
+ ? fn.name
106
+ : (typeof call.name === 'string' ? call.name : 'tool');
107
+ const sanitized = sanitizeFunctionName(callName);
108
+ if (!sanitized || sanitized.toLowerCase() === 'tool')
109
+ return null;
110
+ const rawArgs = fn?.arguments ?? call.arguments ?? {};
111
+ const argsStr = typeof rawArgs === 'string'
112
+ ? rawArgs
113
+ : (() => {
114
+ try {
115
+ return JSON.stringify(rawArgs ?? {});
116
+ }
117
+ catch {
118
+ return '{}';
119
+ }
120
+ })();
121
+ const originalCallId = typeof call.id === 'string' && call.id.trim().length
122
+ ? String(call.id)
123
+ : (typeof call.call_id === 'string' && call.call_id.trim().length ? String(call.call_id) : undefined);
124
+ const callId = originalCallId && originalCallId.trim().length
125
+ ? originalCallId.trim()
126
+ : normalizeFunctionCallId({
127
+ callId: originalCallId,
128
+ fallback: `fc_call_${baseCount + offset}`
129
+ });
130
+ const output = {
131
+ id: allocateOutputId('function_call'),
132
+ type: 'function_call',
133
+ status: 'in_progress',
134
+ name: sanitized,
135
+ call_id: callId,
136
+ arguments: argsStr
137
+ };
138
+ return { output, call_id: callId, name: sanitized, arguments: argsStr };
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ function buildRequiredActionFromNormalized(calls) {
145
+ if (!calls.length)
146
+ return undefined;
147
+ const submitCalls = calls.map((entry) => ({
148
+ id: entry.id,
149
+ type: 'function',
150
+ function: {
151
+ name: entry.name,
152
+ arguments: entry.args
153
+ }
154
+ }));
155
+ return { type: 'submit_tool_outputs', submit_tool_outputs: { tool_calls: submitCalls } };
156
+ }
157
+ function convertChatContentToResponses(content) {
158
+ if (content == null)
159
+ return [];
160
+ if (Array.isArray(content)) {
161
+ return content
162
+ .map((part) => normalizeContentPart(part, []))
163
+ .filter((entry) => !!entry);
164
+ }
165
+ const normalized = normalizeContentPart(content, []);
166
+ return normalized ? [normalized] : [];
167
+ }
168
+ function extractOutputText(parts) {
169
+ if (!parts.length)
170
+ return '';
171
+ const reasoningCollector = [];
172
+ const normalizedTexts = parts
173
+ .map((part) => normalizeContentPart(part, reasoningCollector))
174
+ .filter((entry) => !!entry)
175
+ .map((entry) => (typeof entry.text === 'string' ? entry.text : ''))
176
+ .filter(Boolean);
177
+ const text = normalizedTexts.join('\n').trim();
178
+ return text.length ? text : '';
179
+ }
@@ -0,0 +1,7 @@
1
+ type UnknownRecord = Record<string, unknown>;
2
+ export declare function extractResponsesOutputSegments(response: UnknownRecord | undefined): {
3
+ textParts: string[];
4
+ reasoningParts: string[];
5
+ };
6
+ export declare function normalizeResponsesContentPart(part: unknown, reasoningCollector: string[]): UnknownRecord | null;
7
+ export {};
@@ -0,0 +1,108 @@
1
+ import { extractReasoningSegments, sanitizeReasoningTaggedText } from './reasoning-utils.js';
2
+ function collectMessageTexts(blocks, accumulator) {
3
+ const pushText = (value) => {
4
+ if (typeof value === 'string' && value.trim().length) {
5
+ const cleaned = extractReasoningSegments(value, accumulator.reasoningParts);
6
+ if (cleaned.length) {
7
+ accumulator.textParts.push(cleaned);
8
+ }
9
+ }
10
+ };
11
+ if (typeof blocks === 'string') {
12
+ pushText(blocks);
13
+ return;
14
+ }
15
+ if (!Array.isArray(blocks))
16
+ return;
17
+ for (const block of blocks) {
18
+ if (!block || typeof block !== 'object')
19
+ continue;
20
+ const type = typeof block.type === 'string' ? String(block.type).toLowerCase() : '';
21
+ if (type === 'text' || type === 'input_text' || type === 'output_text' || type === 'commentary') {
22
+ pushText(block.text ?? block.content);
23
+ continue;
24
+ }
25
+ if (Array.isArray(block.content)) {
26
+ collectMessageTexts(block.content, accumulator);
27
+ continue;
28
+ }
29
+ if (typeof block.text === 'string') {
30
+ pushText(block.text);
31
+ }
32
+ }
33
+ }
34
+ export function extractResponsesOutputSegments(response) {
35
+ if (!response || typeof response !== 'object') {
36
+ return { textParts: [], reasoningParts: [] };
37
+ }
38
+ const textParts = [];
39
+ const reasoningParts = [];
40
+ const outputItems = Array.isArray(response.output) ? response.output : [];
41
+ for (const item of outputItems) {
42
+ if (!item || typeof item !== 'object')
43
+ continue;
44
+ const type = typeof item.type === 'string' ? String(item.type).toLowerCase() : '';
45
+ if (type === 'message') {
46
+ const message = item.message && typeof item.message === 'object'
47
+ ? item.message
48
+ : item;
49
+ const content = Array.isArray(message.content) ? message.content : [];
50
+ collectMessageTexts(content, { textParts, reasoningParts });
51
+ continue;
52
+ }
53
+ if (type === 'output_text') {
54
+ if (typeof item.text === 'string') {
55
+ const cleaned = extractReasoningSegments(item.text, reasoningParts);
56
+ if (cleaned.length) {
57
+ textParts.push(cleaned);
58
+ }
59
+ }
60
+ continue;
61
+ }
62
+ if (type === 'reasoning') {
63
+ const content = Array.isArray(item.content) ? item.content : [];
64
+ for (const block of content) {
65
+ if (block && typeof block === 'object' && typeof block.text === 'string') {
66
+ const sanitized = sanitizeReasoningTaggedText(block.text);
67
+ if (sanitized.length)
68
+ reasoningParts.push(sanitized);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return { textParts, reasoningParts };
74
+ }
75
+ export function normalizeResponsesContentPart(part, reasoningCollector) {
76
+ if (part == null) {
77
+ return null;
78
+ }
79
+ if (typeof part === 'string') {
80
+ const cleaned = extractReasoningSegments(part, reasoningCollector);
81
+ return { type: 'output_text', text: cleaned };
82
+ }
83
+ if (typeof part !== 'object') {
84
+ return { type: 'output_text', text: sanitizeReasoningTaggedText(String(part)) };
85
+ }
86
+ const clone = { ...part };
87
+ delete clone._initialText;
88
+ delete clone._hasDelta;
89
+ if (typeof clone.text === 'string') {
90
+ clone.text = extractReasoningSegments(clone.text, reasoningCollector);
91
+ }
92
+ if (clone.output_text && typeof clone.output_text === 'object' && typeof clone.output_text.text === 'string') {
93
+ clone.output_text = {
94
+ ...clone.output_text,
95
+ text: extractReasoningSegments(clone.output_text.text, reasoningCollector)
96
+ };
97
+ }
98
+ if (typeof clone.content === 'string') {
99
+ clone.content = extractReasoningSegments(clone.content, reasoningCollector);
100
+ }
101
+ if (!clone.type) {
102
+ clone.type = 'output_text';
103
+ }
104
+ if (clone.type === 'output_text' && typeof clone.text !== 'string') {
105
+ clone.text = '';
106
+ }
107
+ return clone;
108
+ }
@@ -0,0 +1,28 @@
1
+ import type { BridgeInputItem, BridgeToolDefinition } from './bridge-message-types.js';
2
+ export interface ResponsesRequestContext {
3
+ requestId?: string;
4
+ instructions?: string;
5
+ originalSystemMessages?: string[];
6
+ input?: BridgeInputItem[];
7
+ include?: unknown;
8
+ store?: unknown;
9
+ toolChoice?: unknown;
10
+ parallelToolCalls?: boolean;
11
+ metadata?: Record<string, unknown>;
12
+ responseFormat?: unknown;
13
+ toolsRaw?: BridgeToolDefinition[];
14
+ stream?: boolean;
15
+ instructionsIsRaw?: boolean;
16
+ isChatPayload?: boolean;
17
+ isResponsesPayload?: boolean;
18
+ }
19
+ export interface BuildChatRequestResult {
20
+ request: Record<string, unknown>;
21
+ toolsNormalized?: Array<Record<string, unknown>>;
22
+ }
23
+ export declare function captureResponsesContext(payload: Record<string, unknown>, dto?: {
24
+ route?: {
25
+ requestId?: string;
26
+ };
27
+ }): ResponsesRequestContext;
28
+ export declare function buildChatRequestFromResponses(payload: Record<string, unknown>, context: ResponsesRequestContext): BuildChatRequestResult;
@@ -1,5 +1,6 @@
1
1
  import { normalizeFunctionCallId, normalizeFunctionCallOutputId } from './responses-id-utils.js';
2
2
  import { ensureResponsesInstructions } from './responses-instructions.js';
3
+ import { serializeToolArguments, coerceBridgeRole, serializeToolOutput } from './bridge-message-utils.js';
3
4
  export function captureResponsesContext(payload, dto) {
4
5
  ensureResponsesInstructions(payload);
5
6
  const context = {
@@ -197,7 +198,7 @@ function mapResponsesInputToChat(options) {
197
198
  if (typeof entry.content === 'string') {
198
199
  const directText = (entry.content || '').toString().trim();
199
200
  if (directText.length) {
200
- const normalizedRole = normalizeResponseRole(entry.role || 'user');
201
+ const normalizedRole = coerceBridgeRole(entry.role || 'user');
201
202
  messages.push({ role: normalizedRole, content: directText });
202
203
  continue;
203
204
  }
@@ -215,7 +216,7 @@ function mapResponsesInputToChat(options) {
215
216
  if (callIdRaw && callIdRaw !== callId) {
216
217
  toolCallIdAliases.set(callIdRaw, callId);
217
218
  }
218
- const serialized = normalizeArgumentsBySchema(parsedArgs, name, toolsNormalized).trim();
219
+ const serialized = serializeToolArguments(parsedArgs, name, toolsNormalized).trim();
219
220
  toolNameById.set(callId, name);
220
221
  messages.push({ role: 'assistant', tool_calls: [{ id: callId, type: 'function', function: { name, arguments: serialized } }] });
221
222
  lastToolCallId = callId;
@@ -233,7 +234,7 @@ function mapResponsesInputToChat(options) {
233
234
  }
234
235
  toolCallId = normalized;
235
236
  }
236
- const output = normalizeToolOutput(entry);
237
+ const output = serializeToolOutput(entry);
237
238
  if (toolCallId) {
238
239
  try {
239
240
  let contentStr = output != null ? String(output) : '';
@@ -266,7 +267,7 @@ function mapResponsesInputToChat(options) {
266
267
  messages.push({ role: 'assistant', tool_calls: toolCalls });
267
268
  for (const msg of toolMessages)
268
269
  messages.push(msg);
269
- const normalizedRole = normalizeResponseRole((explicit.role ?? entry.role) || 'user');
270
+ const normalizedRole = coerceBridgeRole((explicit.role ?? entry.role) || 'user');
270
271
  if (typeof text === 'string' && text.trim().length) {
271
272
  messages.push({ role: normalizedRole, content: text.trim() });
272
273
  }
@@ -279,7 +280,7 @@ function mapResponsesInputToChat(options) {
279
280
  messages.push({ role: 'assistant', tool_calls: toolCalls });
280
281
  for (const msg of toolMessages)
281
282
  messages.push(msg);
282
- const normalizedRole = normalizeResponseRole(entry.role || 'user');
283
+ const normalizedRole = coerceBridgeRole(entry.role || 'user');
283
284
  if (typeof text === 'string' && text.trim().length) {
284
285
  messages.push({ role: normalizedRole, content: text.trim() });
285
286
  }
@@ -288,7 +289,7 @@ function mapResponsesInputToChat(options) {
288
289
  try {
289
290
  const t = String(entry.type || '').toLowerCase();
290
291
  if ((t === 'input_text' || t === 'text' || t === 'output_text' || t === 'commentary') && typeof entry.text === 'string') {
291
- const normalizedRole = normalizeResponseRole(entry.role || 'user');
292
+ const normalizedRole = coerceBridgeRole(entry.role || 'user');
292
293
  const s = entry.text;
293
294
  if (s && s.length)
294
295
  messages.push({ role: normalizedRole, content: s });
@@ -339,7 +340,7 @@ function processMessageBlocks(blocks, toolsNormalized, toolNameById, toolCallIdA
339
340
  currentLastCall = null;
340
341
  continue;
341
342
  }
342
- const serialized = normalizeArgumentsBySchema(parsedArgs, name, toolsNormalized).trim();
343
+ const serialized = serializeToolArguments(parsedArgs, name, toolsNormalized).trim();
343
344
  toolNameById.set(callId, name);
344
345
  toolCalls.push({ id: callId, type: 'function', function: { name, arguments: serialized } });
345
346
  currentLastCall = callId;
@@ -357,7 +358,7 @@ function processMessageBlocks(blocks, toolsNormalized, toolNameById, toolCallIdA
357
358
  }
358
359
  toolCallId = normalized;
359
360
  }
360
- const output = normalizeToolOutput(block);
361
+ const output = serializeToolOutput(block);
361
362
  if (toolCallId) {
362
363
  try {
363
364
  let contentStr = output != null ? String(output) : '';
@@ -390,38 +391,6 @@ function processMessageBlocks(blocks, toolsNormalized, toolNameById, toolCallIdA
390
391
  lastCallId: currentLastCall
391
392
  };
392
393
  }
393
- function normalizeResponseRole(role) {
394
- if (typeof role === 'string') {
395
- const normalized = role.toLowerCase();
396
- if (normalized === 'system' || normalized === 'assistant' || normalized === 'user' || normalized === 'tool')
397
- return normalized;
398
- }
399
- return 'user';
400
- }
401
- function normalizeArgumentsBySchema(argsStringOrObj, _functionName, _tools) {
402
- if (typeof argsStringOrObj === 'string')
403
- return argsStringOrObj;
404
- try {
405
- return JSON.stringify(argsStringOrObj ?? {});
406
- }
407
- catch {
408
- return String(argsStringOrObj);
409
- }
410
- }
411
- function normalizeToolOutput(entry) {
412
- const out = entry?.output;
413
- if (typeof out === 'string')
414
- return out;
415
- if (out && typeof out === 'object') {
416
- try {
417
- return JSON.stringify(out);
418
- }
419
- catch {
420
- return String(out);
421
- }
422
- }
423
- return null;
424
- }
425
394
  function normalizeTools(tools) {
426
395
  const normalized = [];
427
396
  for (const tool of tools) {
@@ -0,0 +1,3 @@
1
+ export declare function collectToolCallsFromResponses(response: Record<string, unknown>): Array<Record<string, unknown>>;
2
+ export declare function resolveFinishReason(response: Record<string, unknown>, toolCalls: Array<Record<string, unknown>>): string;
3
+ export declare function buildChatResponseFromResponses(payload: unknown): Record<string, unknown> | unknown;
@@ -0,0 +1,209 @@
1
+ import { sanitizeResponsesFunctionName } from './responses-tool-utils.js';
2
+ import { normalizeFunctionCallId } from './bridge-id-utils.js';
3
+ import { extractOutputSegments } from './output-content-normalizer.js';
4
+ import { sanitizeReasoningTaggedText } from './reasoning-utils.js';
5
+ import { createBridgeActionState, runBridgeActionPipeline } from './bridge-actions.js';
6
+ import { resolveBridgePolicy, resolvePolicyActions } from './bridge-policies.js';
7
+ function selectCallId(entry) {
8
+ const candidates = [
9
+ entry?.call_id,
10
+ entry?.tool_call_id,
11
+ entry?.tool_use_id,
12
+ entry?.id
13
+ ];
14
+ for (const candidate of candidates) {
15
+ if (typeof candidate === 'string' && candidate.trim().length > 0) {
16
+ return candidate.trim();
17
+ }
18
+ }
19
+ return undefined;
20
+ }
21
+ function normalizeToolCall(entry, fallbackPrefix) {
22
+ if (!entry || typeof entry !== 'object')
23
+ return null;
24
+ const fn = entry.function || {};
25
+ const rawName = sanitizeResponsesFunctionName(fn.name ?? entry.name);
26
+ if (!rawName)
27
+ return null;
28
+ const argsRaw = fn.arguments ?? entry.arguments ?? {};
29
+ const argsStr = typeof argsRaw === 'string'
30
+ ? argsRaw
31
+ : (() => {
32
+ try {
33
+ return JSON.stringify(argsRaw ?? {});
34
+ }
35
+ catch {
36
+ return '{}';
37
+ }
38
+ })();
39
+ const callIdRaw = selectCallId(entry);
40
+ const callId = typeof callIdRaw === 'string' && callIdRaw.length
41
+ ? callIdRaw
42
+ : normalizeFunctionCallId({
43
+ callId: callIdRaw,
44
+ fallback: `${fallbackPrefix}_${Math.random().toString(36).slice(2, 10)}`
45
+ });
46
+ return {
47
+ id: callId,
48
+ type: 'function',
49
+ function: {
50
+ name: rawName,
51
+ arguments: argsStr
52
+ }
53
+ };
54
+ }
55
+ export function collectToolCallsFromResponses(response) {
56
+ const collected = [];
57
+ const seenIds = new Set();
58
+ const pushCall = (call, source) => {
59
+ if (!call)
60
+ return;
61
+ const key = typeof call.id === 'string' ? call.id : `${source}_${collected.length}`;
62
+ if (key && seenIds.has(key))
63
+ return;
64
+ if (key)
65
+ seenIds.add(key);
66
+ collected.push(call);
67
+ };
68
+ const required = response?.required_action?.submit_tool_outputs?.tool_calls;
69
+ if (Array.isArray(required)) {
70
+ for (const call of required) {
71
+ pushCall(normalizeToolCall(call, 'req_call'), 'req_call');
72
+ }
73
+ }
74
+ const outputItems = Array.isArray(response.output) ? response.output : [];
75
+ for (const item of outputItems) {
76
+ if (!item || typeof item !== 'object')
77
+ continue;
78
+ const type = typeof item.type === 'string' ? String(item.type).toLowerCase() : '';
79
+ if (type !== 'function_call')
80
+ continue;
81
+ pushCall(normalizeToolCall(item, 'output_call'), 'output_call');
82
+ }
83
+ return collected;
84
+ }
85
+ export function resolveFinishReason(response, toolCalls) {
86
+ const meta = response.metadata && typeof response.metadata === 'object'
87
+ ? response.metadata
88
+ : undefined;
89
+ if (meta && typeof meta.finish_reason === 'string') {
90
+ return meta.finish_reason;
91
+ }
92
+ if (toolCalls.length > 0) {
93
+ return 'tool_calls';
94
+ }
95
+ const status = typeof response.status === 'string' ? response.status.toLowerCase() : '';
96
+ if (status === 'requires_action')
97
+ return 'tool_calls';
98
+ if (status === 'in_progress' || status === 'streaming')
99
+ return 'length';
100
+ if (status === 'cancelled')
101
+ return 'cancelled';
102
+ if (status === 'failed')
103
+ return 'error';
104
+ return 'stop';
105
+ }
106
+ function unwrapResponsesResponse(payload) {
107
+ if (!payload || typeof payload !== 'object')
108
+ return undefined;
109
+ if (typeof payload.object === 'string' && payload.object === 'response') {
110
+ return payload;
111
+ }
112
+ if (Array.isArray(payload.output) ||
113
+ typeof payload.status === 'string' ||
114
+ payload.required_action) {
115
+ return payload;
116
+ }
117
+ let current = payload;
118
+ const visited = new Set();
119
+ while (current && typeof current === 'object' && !Array.isArray(current)) {
120
+ if (visited.has(current))
121
+ break;
122
+ visited.add(current);
123
+ if (typeof current.object === 'string' && current.object === 'response') {
124
+ return current;
125
+ }
126
+ if (current.response && typeof current.response === 'object') {
127
+ current = current.response;
128
+ continue;
129
+ }
130
+ if (current.data && typeof current.data === 'object') {
131
+ current = current.data;
132
+ continue;
133
+ }
134
+ break;
135
+ }
136
+ return undefined;
137
+ }
138
+ export function buildChatResponseFromResponses(payload) {
139
+ if (!payload || typeof payload !== 'object')
140
+ return payload;
141
+ const response = unwrapResponsesResponse(payload);
142
+ if (!response) {
143
+ if (Array.isArray(payload.choices))
144
+ return payload;
145
+ return payload;
146
+ }
147
+ const id = typeof response.id === 'string' ? response.id : `resp_${Date.now()}`;
148
+ const model = response.model;
149
+ const created = typeof response.created_at === 'number'
150
+ ? response.created_at
151
+ : (response.created ?? Math.floor(Date.now() / 1000));
152
+ const usage = response.usage;
153
+ const toolCalls = collectToolCallsFromResponses(response);
154
+ const { textParts, reasoningParts } = extractOutputSegments(response);
155
+ const explicitOutput = typeof response.output_text === 'string' && response.output_text.trim().length
156
+ ? sanitizeReasoningTaggedText(response.output_text)
157
+ : undefined;
158
+ const messageContentText = explicitOutput || textParts.join('\n').trim();
159
+ const messageContent = messageContentText || '';
160
+ const message = {
161
+ role: 'assistant',
162
+ content: messageContent
163
+ };
164
+ try {
165
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
166
+ const policyActions = resolvePolicyActions(bridgePolicy, 'response_inbound');
167
+ if (policyActions?.length) {
168
+ const actionState = createBridgeActionState({
169
+ messages: [message],
170
+ rawResponse: response
171
+ });
172
+ runBridgeActionPipeline({
173
+ stage: 'response_inbound',
174
+ actions: policyActions,
175
+ protocol: bridgePolicy?.protocol ?? 'openai-responses',
176
+ moduleType: bridgePolicy?.moduleType ?? 'openai-responses',
177
+ requestId: typeof response?.id === 'string' ? response.id : undefined,
178
+ state: actionState
179
+ });
180
+ }
181
+ }
182
+ catch {
183
+ // Ignore policy errors
184
+ }
185
+ if (toolCalls.length) {
186
+ message.tool_calls = toolCalls;
187
+ }
188
+ if (reasoningParts.length) {
189
+ message.reasoning_content = reasoningParts.join('\n');
190
+ }
191
+ const finishReason = resolveFinishReason(response, toolCalls);
192
+ const chat = {
193
+ id,
194
+ object: 'chat.completion',
195
+ created,
196
+ model,
197
+ choices: [
198
+ {
199
+ index: 0,
200
+ finish_reason: finishReason,
201
+ message
202
+ }
203
+ ]
204
+ };
205
+ if (usage !== undefined) {
206
+ chat.usage = usage;
207
+ }
208
+ return chat;
209
+ }
@@ -0,0 +1,12 @@
1
+ export type ToolCallIdStyle = 'preserve' | 'fc';
2
+ type BridgeInputItem = Record<string, unknown>;
3
+ export interface CallIdTransformer {
4
+ normalizeCallId(raw: unknown): string;
5
+ normalizeOutputId(callId: string, raw: unknown): string;
6
+ }
7
+ export declare function createToolCallIdTransformer(style: ToolCallIdStyle): CallIdTransformer | null;
8
+ export declare function enforceToolCallIdStyle(input: BridgeInputItem[], transformer: CallIdTransformer): void;
9
+ export declare function resolveToolCallIdStyle(metadata: Record<string, unknown> | undefined): ToolCallIdStyle;
10
+ export declare function stripInternalToolingMetadata(metadata: unknown): void;
11
+ export declare function sanitizeResponsesFunctionName(rawName: unknown): string | undefined;
12
+ export {};