@jsonstudio/llms 0.4.4 → 0.4.5

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 (159) 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-mapping.d.ts +19 -0
  101. package/dist/conversion/shared/tool-mapping.js +124 -0
  102. package/dist/conversion/shared/tool-normalizers.d.ts +4 -0
  103. package/dist/conversion/shared/tool-normalizers.js +84 -0
  104. package/dist/router/virtual-router/bootstrap.js +18 -3
  105. package/dist/router/virtual-router/provider-registry.js +4 -2
  106. package/dist/router/virtual-router/types.d.ts +212 -0
  107. package/dist/sse/index.d.ts +38 -2
  108. package/dist/sse/index.js +27 -0
  109. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +14 -0
  110. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.js +106 -73
  111. package/dist/sse/json-to-sse/chat-json-to-sse-converter.js +6 -2
  112. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +14 -0
  113. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.js +99 -0
  114. package/dist/sse/json-to-sse/index.d.ts +7 -0
  115. package/dist/sse/json-to-sse/index.js +2 -0
  116. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +13 -0
  117. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.js +150 -0
  118. package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +39 -0
  119. package/dist/sse/json-to-sse/sequencers/chat-sequencer.js +49 -3
  120. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +10 -0
  121. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.js +95 -0
  122. package/dist/sse/json-to-sse/sequencers/responses-sequencer.js +31 -5
  123. package/dist/sse/registry/sse-codec-registry.d.ts +32 -0
  124. package/dist/sse/registry/sse-codec-registry.js +30 -1
  125. package/dist/sse/shared/reasoning-dispatcher.d.ts +10 -0
  126. package/dist/sse/shared/reasoning-dispatcher.js +25 -0
  127. package/dist/sse/shared/responses-output-normalizer.d.ts +12 -0
  128. package/dist/sse/shared/responses-output-normalizer.js +45 -0
  129. package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +2 -0
  130. package/dist/sse/shared/serializers/anthropic-event-serializer.js +9 -0
  131. package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +2 -0
  132. package/dist/sse/shared/serializers/gemini-event-serializer.js +5 -0
  133. package/dist/sse/shared/serializers/index.d.ts +41 -0
  134. package/dist/sse/shared/serializers/index.js +2 -0
  135. package/dist/sse/shared/writer.d.ts +127 -0
  136. package/dist/sse/shared/writer.js +37 -1
  137. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +11 -0
  138. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +92 -127
  139. package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +16 -0
  140. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +151 -0
  141. package/dist/sse/sse-to-json/builders/response-builder.d.ts +165 -0
  142. package/dist/sse/sse-to-json/builders/response-builder.js +27 -6
  143. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +114 -0
  144. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +79 -3
  145. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +13 -0
  146. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +160 -0
  147. package/dist/sse/sse-to-json/index.d.ts +7 -0
  148. package/dist/sse/sse-to-json/index.js +2 -0
  149. package/dist/sse/sse-to-json/parsers/sse-parser.js +53 -1
  150. package/dist/sse/types/anthropic-types.d.ts +170 -0
  151. package/dist/sse/types/anthropic-types.js +8 -5
  152. package/dist/sse/types/chat-types.d.ts +10 -0
  153. package/dist/sse/types/chat-types.js +2 -1
  154. package/dist/sse/types/core-interfaces.d.ts +1 -1
  155. package/dist/sse/types/gemini-types.d.ts +116 -0
  156. package/dist/sse/types/gemini-types.js +5 -0
  157. package/dist/sse/types/index.d.ts +5 -2
  158. package/dist/sse/types/index.js +2 -0
  159. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ import type { ChatToolDefinition, MissingField } from '../hub/types/chat-envelope.js';
2
+ type Unknown = Record<string, unknown>;
3
+ type UnknownArray = Unknown[];
4
+ interface OpenAIChatPayload extends Unknown {
5
+ messages: UnknownArray;
6
+ }
7
+ export declare function buildOpenAIChatFromAnthropic(payload: unknown): OpenAIChatPayload;
8
+ export declare function buildAnthropicFromOpenAIChat(oa: unknown): Unknown;
9
+ export declare function buildAnthropicRequestFromOpenAIChat(chatReq: unknown): Unknown;
10
+ export declare function mapAnthropicToolsToChat(rawTools: unknown, missing?: MissingField[]): ChatToolDefinition[] | undefined;
11
+ export declare function mapChatToolsToAnthropicTools(rawTools: unknown): UnknownArray | undefined;
12
+ export {};
@@ -0,0 +1,587 @@
1
+ import { createBridgeActionState, runBridgeActionPipeline } from './bridge-actions.js';
2
+ import { resolveBridgePolicy, resolvePolicyActions } from './bridge-policies.js';
3
+ import { normalizeChatMessageContent } from './chat-output-normalizer.js';
4
+ import { mapBridgeToolsToChat, mapChatToolsToBridge } from './tool-mapping.js';
5
+ import { jsonClone } from '../hub/types/json.js';
6
+ function isObject(v) {
7
+ return !!v && typeof v === 'object' && !Array.isArray(v);
8
+ }
9
+ function safeJson(v) {
10
+ try {
11
+ return JSON.stringify(v ?? {});
12
+ }
13
+ catch {
14
+ return '{}';
15
+ }
16
+ }
17
+ function flattenAnthropicText(content) {
18
+ if (content == null)
19
+ return '';
20
+ if (typeof content === 'string')
21
+ return content;
22
+ if (Array.isArray(content))
23
+ return content.map(flattenAnthropicText).join('');
24
+ if (typeof content === 'object') {
25
+ const t = String(content.type || '').toLowerCase();
26
+ if (t === 'text' && typeof content.text === 'string')
27
+ return String(content.text);
28
+ if (Array.isArray(content.content))
29
+ return content.content.map(flattenAnthropicText).join('');
30
+ if (typeof content.content === 'string')
31
+ return String(content.content);
32
+ }
33
+ return '';
34
+ }
35
+ function requireTrimmedString(value, context) {
36
+ if (typeof value !== 'string') {
37
+ throw new Error(`Anthropic bridge constraint violated: ${context} must be a string`);
38
+ }
39
+ const trimmed = value.trim();
40
+ if (!trimmed.length) {
41
+ throw new Error(`Anthropic bridge constraint violated: ${context} must not be empty`);
42
+ }
43
+ return trimmed;
44
+ }
45
+ function requireSystemText(block, context) {
46
+ const text = flattenAnthropicText(block).trim();
47
+ if (!text) {
48
+ throw new Error(`Anthropic bridge constraint violated: ${context} must contain text`);
49
+ }
50
+ return text;
51
+ }
52
+ export function buildOpenAIChatFromAnthropic(payload) {
53
+ const newMessages = [];
54
+ const body = isObject(payload) ? payload : {};
55
+ const rawSystem = body.system;
56
+ const systemBlocks = Array.isArray(rawSystem)
57
+ ? rawSystem
58
+ : rawSystem !== undefined && rawSystem !== null
59
+ ? [rawSystem]
60
+ : [];
61
+ for (const block of systemBlocks) {
62
+ const text = requireSystemText(block, 'system entry');
63
+ newMessages.push({ role: 'system', content: text });
64
+ }
65
+ const msgs = Array.isArray(body.messages) ? body.messages : [];
66
+ for (const m of msgs) {
67
+ if (!m || typeof m !== 'object')
68
+ continue;
69
+ const role = typeof m.role === 'string' ? String(m.role) : 'user';
70
+ const content = m.content;
71
+ if (!Array.isArray(content)) {
72
+ const text = flattenAnthropicText(content);
73
+ if (text) {
74
+ const normalized = normalizeChatMessageContent(text);
75
+ const message = {
76
+ role,
77
+ content: normalized.contentText ?? text
78
+ };
79
+ if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
80
+ message.reasoning_content = normalized.reasoningText.trim();
81
+ }
82
+ newMessages.push(message);
83
+ }
84
+ continue;
85
+ }
86
+ const textParts = [];
87
+ const toolCalls = [];
88
+ const reasoningParts = [];
89
+ const toolResults = [];
90
+ for (const block of content) {
91
+ if (!block || typeof block !== 'object')
92
+ continue;
93
+ const t = String(block.type || '').toLowerCase();
94
+ if (t === 'text' && typeof block.text === 'string') {
95
+ const s = block.text.trim();
96
+ if (s)
97
+ textParts.push(s);
98
+ }
99
+ else if (t === 'thinking' || t === 'reasoning') {
100
+ const thinkingText = flattenAnthropicText(block).trim();
101
+ if (thinkingText) {
102
+ reasoningParts.push(thinkingText);
103
+ }
104
+ }
105
+ else if (t === 'tool_use') {
106
+ const name = requireTrimmedString(block.name, 'tool_use.name');
107
+ const id = requireTrimmedString(block.id, 'tool_use.id');
108
+ const input = block.input ?? {};
109
+ const args = safeJson(input);
110
+ toolCalls.push({ id, type: 'function', function: { name, arguments: args } });
111
+ }
112
+ else if (t === 'tool_result') {
113
+ const callId = requireTrimmedString(block.tool_call_id ??
114
+ block.call_id ??
115
+ block.tool_use_id ??
116
+ block.id, 'tool_result.tool_use_id');
117
+ let contentStr = '';
118
+ const c = block.content;
119
+ if (typeof c === 'string')
120
+ contentStr = c;
121
+ else if (c != null) {
122
+ try {
123
+ contentStr = JSON.stringify(c);
124
+ }
125
+ catch {
126
+ contentStr = String(c);
127
+ }
128
+ }
129
+ toolResults.push({ role: 'tool', tool_call_id: callId, content: contentStr });
130
+ }
131
+ }
132
+ const combinedText = textParts.join('\n');
133
+ const normalized = normalizeChatMessageContent(combinedText);
134
+ const mergedReasoning = [...reasoningParts];
135
+ if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
136
+ mergedReasoning.push(normalized.reasoningText.trim());
137
+ }
138
+ const hasText = typeof normalized.contentText === 'string' && normalized.contentText.length > 0;
139
+ const hasReasoning = mergedReasoning.length > 0;
140
+ if (hasText || toolCalls.length > 0 || hasReasoning) {
141
+ const msg = {
142
+ role,
143
+ content: normalized.contentText ?? combinedText ?? ''
144
+ };
145
+ if (toolCalls.length)
146
+ msg.tool_calls = toolCalls;
147
+ if (hasReasoning) {
148
+ msg.reasoning_content = mergedReasoning.join('\n');
149
+ }
150
+ newMessages.push(msg);
151
+ }
152
+ for (const tr of toolResults)
153
+ newMessages.push(tr);
154
+ }
155
+ const request = { messages: newMessages };
156
+ if (typeof body.model === 'string')
157
+ request.model = body.model;
158
+ if (typeof body.max_tokens === 'number')
159
+ request.max_tokens = body.max_tokens;
160
+ if (typeof body.temperature === 'number')
161
+ request.temperature = body.temperature;
162
+ if (typeof body.top_p === 'number')
163
+ request.top_p = body.top_p;
164
+ if (typeof body.stream === 'boolean')
165
+ request.stream = body.stream;
166
+ if ('tool_choice' in body)
167
+ request.tool_choice = body.tool_choice;
168
+ const normalizedTools = mapAnthropicToolsToChat(body.tools);
169
+ if (normalizedTools !== undefined) {
170
+ request.tools = normalizedTools;
171
+ }
172
+ try {
173
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
174
+ const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
175
+ if (actions?.length) {
176
+ const actionState = createBridgeActionState({
177
+ messages: newMessages,
178
+ rawRequest: body
179
+ });
180
+ runBridgeActionPipeline({
181
+ stage: 'request_inbound',
182
+ actions,
183
+ protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
184
+ moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
185
+ requestId: typeof body?.id === 'string' ? String(body.id) : undefined,
186
+ state: actionState
187
+ });
188
+ }
189
+ }
190
+ catch {
191
+ // ignore policy failures
192
+ }
193
+ return request;
194
+ }
195
+ export function buildAnthropicFromOpenAIChat(oa) {
196
+ const mapFinishReason = (reason) => {
197
+ if (typeof reason !== 'string' || !reason.trim().length) {
198
+ return undefined;
199
+ }
200
+ const mapping = {
201
+ stop: 'end_turn',
202
+ length: 'max_tokens',
203
+ tool_calls: 'tool_use',
204
+ content_filter: 'stop_sequence',
205
+ function_call: 'tool_use'
206
+ };
207
+ return mapping[reason.trim()];
208
+ };
209
+ const body = isObject(oa) ? oa : {};
210
+ const choices = Array.isArray(body.choices) ? body.choices : [];
211
+ const primary = choices[0] && typeof choices[0] === 'object' ? choices[0] : {};
212
+ const msg = isObject(primary.message) ? primary.message : {};
213
+ const role = typeof msg.role === 'string' ? msg.role : 'assistant';
214
+ const blocks = [];
215
+ const content = msg?.content;
216
+ const contentArray = Array.isArray(content) ? content : undefined;
217
+ if (typeof content === 'string') {
218
+ blocks.push({ type: 'text', text: content });
219
+ }
220
+ if (contentArray) {
221
+ const text = contentArray
222
+ .map((p) => p && typeof p.text === 'string'
223
+ ? String(p.text)
224
+ : typeof p === 'string'
225
+ ? p
226
+ : '')
227
+ .filter(Boolean)
228
+ .join('');
229
+ if (text) {
230
+ blocks.push({ type: 'text', text });
231
+ }
232
+ }
233
+ const reasoningField = typeof msg?.reasoning_content === 'string'
234
+ ? msg.reasoning_content
235
+ : typeof msg?.reasoning === 'string'
236
+ ? msg.reasoning
237
+ : undefined;
238
+ if (reasoningField && reasoningField.trim().length) {
239
+ blocks.push({ type: 'thinking', text: reasoningField.trim() });
240
+ }
241
+ const toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
242
+ for (const tc of toolCalls) {
243
+ try {
244
+ const id = requireTrimmedString(tc?.id, 'chat.tool_call.id');
245
+ const fn = isObject(tc?.function) ? tc.function : {};
246
+ const name = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
247
+ const argsRaw = fn.arguments;
248
+ let input;
249
+ if (typeof argsRaw === 'string') {
250
+ try {
251
+ input = JSON.parse(argsRaw);
252
+ }
253
+ catch {
254
+ input = { _raw: argsRaw };
255
+ }
256
+ }
257
+ else {
258
+ input = argsRaw ?? {};
259
+ }
260
+ blocks.push({ type: 'tool_use', id, name, input });
261
+ }
262
+ catch {
263
+ // ignore malformed tool call
264
+ }
265
+ }
266
+ const usageChunk = isObject(body.usage) ? body.usage : {};
267
+ const inputTokens = Number(usageChunk.prompt_tokens ?? usageChunk.input_tokens ?? 0);
268
+ const outputTokens = Number(usageChunk.completion_tokens ?? usageChunk.output_tokens ?? 0);
269
+ const finishReason = Array.isArray(body.choices) &&
270
+ body.choices[0] &&
271
+ typeof body.choices[0]?.finish_reason === 'string'
272
+ ? String(body.choices[0].finish_reason).trim()
273
+ : undefined;
274
+ const stopReason = mapFinishReason(finishReason);
275
+ return {
276
+ id: `resp_${Date.now()}`,
277
+ type: 'message',
278
+ role,
279
+ model: String(body.model || 'unknown'),
280
+ created: Math.floor(Date.now() / 1000),
281
+ content: blocks,
282
+ usage: inputTokens || outputTokens ? { input_tokens: inputTokens, output_tokens: outputTokens } : undefined,
283
+ ...(stopReason ? { stop_reason: stopReason } : {})
284
+ };
285
+ }
286
+ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
287
+ const requestBody = isObject(chatReq) ? chatReq : {};
288
+ const model = String(requestBody?.model || 'unknown');
289
+ const messages = [];
290
+ try {
291
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
292
+ const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
293
+ if (actions?.length && Array.isArray(requestBody.messages)) {
294
+ const actionState = createBridgeActionState({
295
+ messages: requestBody.messages,
296
+ rawRequest: requestBody
297
+ });
298
+ runBridgeActionPipeline({
299
+ stage: 'request_outbound',
300
+ actions,
301
+ protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
302
+ moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
303
+ state: actionState
304
+ });
305
+ }
306
+ }
307
+ catch {
308
+ // ignore policy errors
309
+ }
310
+ const collectText = (val) => {
311
+ if (!val)
312
+ return '';
313
+ if (typeof val === 'string')
314
+ return val;
315
+ if (Array.isArray(val))
316
+ return val.map(collectText).join('');
317
+ if (typeof val === 'object') {
318
+ if (typeof val.text === 'string')
319
+ return String(val.text);
320
+ if (Array.isArray(val.content))
321
+ return collectText(val.content);
322
+ }
323
+ return '';
324
+ };
325
+ const msgs = Array.isArray(requestBody?.messages) ? requestBody.messages : [];
326
+ const knownToolCallIds = new Set();
327
+ for (const m of msgs) {
328
+ if (!m || typeof m !== 'object')
329
+ continue;
330
+ const role = String(m.role || 'user');
331
+ if (role !== 'assistant')
332
+ continue;
333
+ const toolCalls = m.tool_calls;
334
+ if (!Array.isArray(toolCalls))
335
+ continue;
336
+ for (const tc of toolCalls) {
337
+ if (!tc || typeof tc !== 'object')
338
+ continue;
339
+ const id = requireTrimmedString(tc.id, 'chat.tool_call.id');
340
+ knownToolCallIds.add(id);
341
+ }
342
+ }
343
+ const systemBlocks = [];
344
+ const pushSystemBlock = (text) => {
345
+ const trimmed = text.trim();
346
+ if (trimmed)
347
+ systemBlocks.push({ type: 'text', text: trimmed });
348
+ };
349
+ try {
350
+ const sys = requestBody.system;
351
+ const ingestSystem = (val) => {
352
+ if (!val)
353
+ return;
354
+ if (typeof val === 'string') {
355
+ pushSystemBlock(requireSystemText(val, 'top-level system'));
356
+ return;
357
+ }
358
+ if (Array.isArray(val)) {
359
+ for (const entry of val)
360
+ ingestSystem(entry);
361
+ return;
362
+ }
363
+ if (typeof val === 'object') {
364
+ pushSystemBlock(requireSystemText(val, 'top-level system'));
365
+ return;
366
+ }
367
+ throw new Error('Anthropic bridge constraint violated: unsupported system payload type');
368
+ };
369
+ ingestSystem(sys);
370
+ }
371
+ catch {
372
+ // ignore system pre-scan errors
373
+ }
374
+ for (const m of msgs) {
375
+ if (!m || typeof m !== 'object')
376
+ continue;
377
+ const role = String(m.role || 'user');
378
+ const text = collectText(m.content).trim();
379
+ if (role === 'system') {
380
+ if (!text) {
381
+ throw new Error('Anthropic bridge constraint violated: Chat system message must contain text');
382
+ }
383
+ pushSystemBlock(text);
384
+ continue;
385
+ }
386
+ if (role === 'tool') {
387
+ const toolCallId = requireTrimmedString(m.tool_call_id ?? m.call_id ?? m.tool_use_id ?? m.id, 'tool_result.tool_call_id');
388
+ if (!knownToolCallIds.has(toolCallId)) {
389
+ throw new Error(`Anthropic bridge constraint violated: tool result ${toolCallId} has no matching tool call`);
390
+ }
391
+ const block = {
392
+ type: 'tool_result',
393
+ content: text
394
+ };
395
+ block.tool_use_id = toolCallId;
396
+ messages.push({
397
+ role: 'user',
398
+ content: [block]
399
+ });
400
+ continue;
401
+ }
402
+ const blocks = [];
403
+ if (text) {
404
+ blocks.push({ type: 'text', text });
405
+ }
406
+ const toolCalls = Array.isArray(m.tool_calls) ? m.tool_calls : [];
407
+ for (const tc of toolCalls) {
408
+ if (!tc || typeof tc !== 'object')
409
+ continue;
410
+ const id = requireTrimmedString(tc.id, 'chat.tool_call.id');
411
+ const fn = tc.function || {};
412
+ const name = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
413
+ const argsRaw = fn.arguments;
414
+ let input;
415
+ if (typeof argsRaw === 'string') {
416
+ try {
417
+ input = JSON.parse(argsRaw);
418
+ }
419
+ catch {
420
+ input = { _raw: argsRaw };
421
+ }
422
+ }
423
+ else {
424
+ input = argsRaw ?? {};
425
+ }
426
+ blocks.push({ type: 'tool_use', id, name, input });
427
+ }
428
+ if (blocks.length > 0) {
429
+ messages.push({ role, content: blocks });
430
+ }
431
+ }
432
+ const out = { model };
433
+ if (systemBlocks.length) {
434
+ out.system = systemBlocks;
435
+ }
436
+ out.messages = messages;
437
+ const anthropicTools = mapChatToolsToAnthropicTools(requestBody.tools);
438
+ if (anthropicTools !== undefined) {
439
+ out.tools = anthropicTools;
440
+ }
441
+ try {
442
+ if (requestBody.metadata && typeof requestBody.metadata === 'object') {
443
+ out.metadata = JSON.parse(JSON.stringify(requestBody.metadata));
444
+ }
445
+ }
446
+ catch {
447
+ // best-effort metadata clone
448
+ }
449
+ const mt = Number(requestBody.max_tokens ??
450
+ requestBody.maxTokens ??
451
+ NaN);
452
+ if (Number.isFinite(mt) && mt > 0)
453
+ out.max_tokens = mt;
454
+ if (typeof requestBody.temperature === 'number') {
455
+ out.temperature = Number(requestBody.temperature);
456
+ }
457
+ if (typeof requestBody.top_p === 'number') {
458
+ out.top_p = Number(requestBody.top_p);
459
+ }
460
+ if (typeof requestBody.stream === 'boolean') {
461
+ out.stream = Boolean(requestBody.stream);
462
+ }
463
+ const stop = requestBody.stop;
464
+ if (typeof stop === 'string' && stop.trim()) {
465
+ out.stop_sequences = [stop.trim()];
466
+ }
467
+ else if (Array.isArray(stop) && stop.length > 0) {
468
+ out.stop_sequences = stop.map((s) => String(s)).filter(Boolean);
469
+ }
470
+ return out;
471
+ }
472
+ function isPlainRecord(value) {
473
+ return !!value && typeof value === 'object' && !Array.isArray(value);
474
+ }
475
+ function coerceJsonValue(value) {
476
+ if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
477
+ return value;
478
+ }
479
+ if (Array.isArray(value)) {
480
+ return value.map((entry) => coerceJsonValue(entry));
481
+ }
482
+ if (isPlainRecord(value)) {
483
+ const obj = {};
484
+ for (const [key, entry] of Object.entries(value)) {
485
+ obj[key] = coerceJsonValue(entry);
486
+ }
487
+ return obj;
488
+ }
489
+ return String(value ?? '');
490
+ }
491
+ function cloneAnthropicSchema(value) {
492
+ if (isPlainRecord(value)) {
493
+ try {
494
+ return JSON.parse(JSON.stringify(value));
495
+ }
496
+ catch {
497
+ return value;
498
+ }
499
+ }
500
+ return { type: 'object', properties: {} };
501
+ }
502
+ function prepareAnthropicBridgeTools(rawTools, missing) {
503
+ if (!Array.isArray(rawTools) || rawTools.length === 0) {
504
+ return undefined;
505
+ }
506
+ const result = [];
507
+ rawTools.forEach((entry, index) => {
508
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
509
+ missing?.push({
510
+ path: `tools[${index}]`,
511
+ reason: 'invalid_entry',
512
+ originalValue: jsonClone(coerceJsonValue(entry))
513
+ });
514
+ return;
515
+ }
516
+ const name = typeof entry.name === 'string'
517
+ ? entry.name
518
+ : undefined;
519
+ if (!name) {
520
+ missing?.push({ path: `tools[${index}].name`, reason: 'missing_name' });
521
+ return;
522
+ }
523
+ const description = typeof entry.description === 'string'
524
+ ? entry.description
525
+ : undefined;
526
+ const schemaSource = entry.input_schema;
527
+ const parameters = cloneAnthropicSchema(schemaSource);
528
+ result.push({
529
+ type: 'function',
530
+ function: {
531
+ name,
532
+ description,
533
+ parameters
534
+ }
535
+ });
536
+ });
537
+ return result.length ? result : undefined;
538
+ }
539
+ function convertBridgeToolToAnthropic(def) {
540
+ if (!def || typeof def !== 'object') {
541
+ return null;
542
+ }
543
+ const fnNode = def.function && typeof def.function === 'object' ? def.function : undefined;
544
+ const name = typeof fnNode?.name === 'string'
545
+ ? fnNode.name
546
+ : typeof def.name === 'string'
547
+ ? def.name
548
+ : undefined;
549
+ if (!name) {
550
+ return null;
551
+ }
552
+ const description = typeof fnNode?.description === 'string'
553
+ ? fnNode.description
554
+ : typeof def.description === 'string'
555
+ ? def.description
556
+ : undefined;
557
+ const schemaSource = fnNode?.parameters ?? def.parameters;
558
+ const inputSchema = cloneAnthropicSchema(schemaSource);
559
+ const tool = {
560
+ name,
561
+ input_schema: inputSchema
562
+ };
563
+ if (description) {
564
+ tool.description = description;
565
+ }
566
+ return tool;
567
+ }
568
+ export function mapAnthropicToolsToChat(rawTools, missing) {
569
+ const prepared = prepareAnthropicBridgeTools(rawTools, missing);
570
+ if (prepared === undefined) {
571
+ return undefined;
572
+ }
573
+ return mapBridgeToolsToChat(prepared);
574
+ }
575
+ export function mapChatToolsToAnthropicTools(rawTools) {
576
+ if (!Array.isArray(rawTools) || rawTools.length === 0) {
577
+ return undefined;
578
+ }
579
+ const bridgeDefs = mapChatToolsToBridge(rawTools);
580
+ if (!bridgeDefs || !bridgeDefs.length) {
581
+ return undefined;
582
+ }
583
+ const converted = bridgeDefs
584
+ .map((def) => convertBridgeToolToAnthropic(def))
585
+ .filter((entry) => !!entry);
586
+ return converted.length ? converted : undefined;
587
+ }
@@ -0,0 +1,39 @@
1
+ type UnknownRecord = Record<string, unknown>;
2
+ export type BridgeActionStage = 'request_inbound' | 'request_outbound' | 'response_inbound' | 'response_outbound';
3
+ export interface BridgeActionDescriptor {
4
+ name: string;
5
+ options?: Record<string, unknown>;
6
+ }
7
+ export interface BridgeActionState {
8
+ messages: Array<UnknownRecord>;
9
+ requiredAction?: UnknownRecord;
10
+ capturedToolResults?: Array<{
11
+ tool_call_id?: string;
12
+ call_id?: string;
13
+ output?: unknown;
14
+ name?: string;
15
+ }>;
16
+ rawRequest?: UnknownRecord;
17
+ rawResponse?: UnknownRecord;
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+ export interface BridgeActionContext {
21
+ stage: BridgeActionStage;
22
+ protocol?: string;
23
+ moduleType?: string;
24
+ requestId?: string;
25
+ descriptor: BridgeActionDescriptor;
26
+ state: BridgeActionState;
27
+ }
28
+ export type BridgeAction = (context: BridgeActionContext) => void;
29
+ export declare function registerBridgeAction(name: string, action: BridgeAction): void;
30
+ export declare function createBridgeActionState(seed?: Partial<BridgeActionState>): BridgeActionState;
31
+ export declare function runBridgeActionPipeline(options: {
32
+ stage: BridgeActionStage;
33
+ actions?: BridgeActionDescriptor[];
34
+ protocol?: string;
35
+ moduleType?: string;
36
+ requestId?: string;
37
+ state: BridgeActionState;
38
+ }): void;
39
+ export {};