@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
@@ -1,5 +1,9 @@
1
1
  import { isJsonObject, jsonClone } from '../types/json.js';
2
2
  import { buildOpenAIChatFromAnthropic, buildAnthropicRequestFromOpenAIChat } from '../../codecs/anthropic-openai-codec.js';
3
+ import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
4
+ import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
5
+ import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../shared/metadata-passthrough.js';
6
+ import { mapAnthropicToolsToChat } from '../../shared/anthropic-message-utils.js';
3
7
  const ANTHROPIC_PARAMETER_KEYS = [
4
8
  'model',
5
9
  'temperature',
@@ -28,55 +32,6 @@ const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
28
32
  ]);
29
33
  const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
30
34
  const PASSTHROUGH_PARAMETERS = ['tool_choice'];
31
- function encodePassthroughParameters(chat) {
32
- if (!chat.parameters)
33
- return undefined;
34
- const encoded = {};
35
- for (const key of PASSTHROUGH_PARAMETERS) {
36
- const value = chat.parameters[key];
37
- if (value === undefined)
38
- continue;
39
- try {
40
- encoded[`${PASSTHROUGH_METADATA_PREFIX}${key}`] = JSON.stringify(value);
41
- }
42
- catch {
43
- continue;
44
- }
45
- }
46
- return Object.keys(encoded).length ? encoded : undefined;
47
- }
48
- function extractPassthroughParameters(metadataField) {
49
- if (!isJsonObject(metadataField))
50
- return {};
51
- const cloned = jsonClone(metadataField);
52
- const passthrough = {};
53
- let metadataMutated = false;
54
- let hasPassthrough = false;
55
- for (const key of Object.keys(cloned)) {
56
- if (!key.startsWith(PASSTHROUGH_METADATA_PREFIX))
57
- continue;
58
- const paramKey = key.slice(PASSTHROUGH_METADATA_PREFIX.length);
59
- if (!PASSTHROUGH_PARAMETERS.includes(paramKey))
60
- continue;
61
- const rawValue = cloned[key];
62
- if (typeof rawValue !== 'string')
63
- continue;
64
- try {
65
- const parsed = rawValue ? JSON.parse(rawValue) : undefined;
66
- passthrough[paramKey] = parsed;
67
- delete cloned[key];
68
- metadataMutated = true;
69
- hasPassthrough = true;
70
- }
71
- catch {
72
- continue;
73
- }
74
- }
75
- return {
76
- metadata: metadataMutated ? (Object.keys(cloned).length ? cloned : undefined) : cloned,
77
- passthrough: hasPassthrough ? passthrough : undefined
78
- };
79
- }
80
35
  function collectParameters(payload) {
81
36
  const params = {};
82
37
  for (const key of ANTHROPIC_PARAMETER_KEYS) {
@@ -89,34 +44,6 @@ function collectParameters(payload) {
89
44
  }
90
45
  return Object.keys(params).length ? params : undefined;
91
46
  }
92
- function normalizeAnthropicTools(rawTools, missing) {
93
- if (!Array.isArray(rawTools) || rawTools.length === 0)
94
- return undefined;
95
- const tools = [];
96
- rawTools.forEach((entry, index) => {
97
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
98
- missing.push({ path: `tools[${index}]`, reason: 'invalid_entry', originalValue: jsonClone(entry) });
99
- return;
100
- }
101
- const nameValue = entry.name;
102
- if (typeof nameValue !== 'string' || !nameValue.trim())
103
- return;
104
- const desc = entry.description;
105
- const schema = entry.input_schema;
106
- const parameters = schema && typeof schema === 'object' && !Array.isArray(schema)
107
- ? schema
108
- : { type: 'object', properties: {} };
109
- tools.push({
110
- type: 'function',
111
- function: {
112
- name: nameValue,
113
- description: typeof desc === 'string' ? desc : undefined,
114
- parameters
115
- }
116
- });
117
- });
118
- return tools.length ? tools : undefined;
119
- }
120
47
  function normalizeToolContent(content) {
121
48
  if (typeof content === 'string')
122
49
  return content;
@@ -149,43 +76,6 @@ function collectToolOutputsFromMessages(messages, missing) {
149
76
  });
150
77
  return outputs.length ? outputs : undefined;
151
78
  }
152
- function collectExtraFields(payload) {
153
- const extras = {};
154
- for (const key of Object.keys(payload)) {
155
- if (ANTHROPIC_TOP_LEVEL_FIELDS.has(key))
156
- continue;
157
- extras[key] = payload[key];
158
- }
159
- return Object.keys(extras).length ? extras : undefined;
160
- }
161
- function collectSystemSegments(systemField) {
162
- if (!systemField)
163
- return [];
164
- const flatten = (value) => {
165
- if (typeof value === 'string')
166
- return value;
167
- if (Array.isArray(value)) {
168
- return value.map(v => flatten(v)).filter(Boolean).join('\n');
169
- }
170
- if (value && typeof value === 'object') {
171
- const obj = value;
172
- if (typeof obj.text === 'string')
173
- return obj.text;
174
- if (typeof obj.content === 'string')
175
- return obj.content;
176
- if (Array.isArray(obj.content))
177
- return obj.content.map(v => flatten(v)).join('\n');
178
- }
179
- return '';
180
- };
181
- const text = flatten(systemField).trim();
182
- return text ? [text] : [];
183
- }
184
- function systemSegmentsToAnthropicBlocks(segments) {
185
- if (!segments || !segments.length)
186
- return undefined;
187
- return segments.map(seg => ({ type: 'text', text: seg }));
188
- }
189
79
  export class AnthropicSemanticMapper {
190
80
  async toChat(format, ctx) {
191
81
  const payload = (format.payload ?? {});
@@ -194,38 +84,55 @@ export class AnthropicSemanticMapper {
194
84
  missing.push({ path: 'messages', reason: 'absent' });
195
85
  if (typeof payload.model !== 'string')
196
86
  missing.push({ path: 'model', reason: 'absent' });
197
- const { messages } = buildOpenAIChatFromAnthropic(payload);
87
+ const openaiPayload = buildOpenAIChatFromAnthropic(payload);
88
+ const messages = Array.isArray(openaiPayload.messages)
89
+ ? openaiPayload.messages
90
+ : [];
198
91
  const toolOutputs = collectToolOutputsFromMessages(messages, missing);
199
- const tools = payload.tools ? normalizeAnthropicTools(payload.tools, missing) : undefined;
92
+ const tools = mapAnthropicToolsToChat(payload.tools, missing);
200
93
  let parameters = collectParameters(payload);
201
- const passthrough = extractPassthroughParameters(payload.metadata);
94
+ const passthrough = extractMetadataPassthrough(payload.metadata, {
95
+ prefix: PASSTHROUGH_METADATA_PREFIX,
96
+ keys: PASSTHROUGH_PARAMETERS
97
+ });
202
98
  if (passthrough.passthrough) {
203
99
  parameters = { ...(parameters || {}), ...passthrough.passthrough };
204
100
  }
205
- const systemSegments = collectSystemSegments(payload.system);
206
101
  const metadata = { context: ctx };
207
- if (systemSegments.length) {
208
- metadata.systemInstructions = systemSegments;
209
- }
210
- if (payload.system !== undefined) {
211
- metadata.rawSystem = jsonClone(payload.system);
212
- }
213
102
  if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
214
103
  metadata.toolsFieldPresent = true;
215
104
  }
216
105
  if (missing.length) {
217
106
  metadata.missingFields = missing;
218
107
  }
219
- const extraFields = collectExtraFields(payload);
220
- if (extraFields) {
221
- metadata.extraFields = extraFields;
222
- }
223
108
  if (passthrough.metadata) {
224
109
  metadata.providerMetadata = passthrough.metadata;
225
110
  }
226
111
  else if (payload.metadata && isJsonObject(payload.metadata)) {
227
112
  metadata.providerMetadata = jsonClone(payload.metadata);
228
113
  }
114
+ try {
115
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
116
+ const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
117
+ if (actions?.length) {
118
+ const actionState = createBridgeActionState({
119
+ messages: messages,
120
+ rawRequest: payload,
121
+ metadata: metadata
122
+ });
123
+ runBridgeActionPipeline({
124
+ stage: 'request_inbound',
125
+ actions,
126
+ protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
127
+ moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
128
+ requestId: ctx.requestId,
129
+ state: actionState
130
+ });
131
+ }
132
+ }
133
+ catch {
134
+ // best-effort metadata extraction
135
+ }
229
136
  return {
230
137
  messages,
231
138
  tools,
@@ -242,10 +149,13 @@ export class AnthropicSemanticMapper {
242
149
  const baseRequest = {
243
150
  ...(chat.parameters || {}),
244
151
  model,
245
- messages: chat.metadata?.systemInstructions ? chat.messages.filter(message => message.role !== 'system') : chat.messages,
152
+ messages: chat.messages,
246
153
  tools: chat.tools
247
154
  };
248
- const passthroughMetadata = encodePassthroughParameters(chat);
155
+ const passthroughMetadata = encodeMetadataPassthrough(chat.parameters, {
156
+ prefix: PASSTHROUGH_METADATA_PREFIX,
157
+ keys: PASSTHROUGH_PARAMETERS
158
+ });
249
159
  if (passthroughMetadata) {
250
160
  const rawMetadata = baseRequest.metadata;
251
161
  const existingMetadata = isJsonObject(rawMetadata)
@@ -259,43 +169,47 @@ export class AnthropicSemanticMapper {
259
169
  if (baseRequest.max_output_tokens && !baseRequest.max_tokens) {
260
170
  baseRequest.max_tokens = baseRequest.max_output_tokens;
261
171
  }
262
- const systemSegmentsRaw = Array.isArray(chat.metadata?.systemInstructions)
263
- ? chat.metadata?.systemInstructions
264
- : undefined;
265
- const systemSegments = systemSegmentsRaw?.filter((seg) => typeof seg === 'string' && seg.trim().length > 0);
266
- if (chat.metadata?.rawSystem !== undefined) {
267
- baseRequest.system = chat.metadata.rawSystem;
268
- }
269
- else if (systemSegments && systemSegments.length > 0) {
270
- baseRequest.system = systemSegmentsToAnthropicBlocks(systemSegments);
271
- }
272
172
  if (isJsonObject(chat.metadata?.providerMetadata)) {
273
173
  baseRequest.metadata = jsonClone(chat.metadata?.providerMetadata);
274
174
  }
275
175
  if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
276
176
  baseRequest.tools = [];
277
177
  }
278
- if (isJsonObject(chat.metadata?.extraFields)) {
279
- for (const [key, value] of Object.entries(chat.metadata?.extraFields)) {
280
- if (baseRequest[key] === undefined) {
281
- baseRequest[key] = value;
282
- }
283
- }
284
- }
285
- const payload = buildAnthropicRequestFromOpenAIChat(baseRequest);
286
- if (chat.metadata?.rawSystem !== undefined) {
287
- payload.system = chat.metadata.rawSystem;
288
- }
178
+ const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
179
+ const payload = JSON.parse(JSON.stringify(payloadSource));
289
180
  if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
290
181
  payload.tools = [];
291
182
  }
292
- if (isJsonObject(chat.metadata?.extraFields)) {
293
- for (const [key, value] of Object.entries(chat.metadata?.extraFields)) {
294
- if (payload[key] === undefined) {
295
- payload[key] = value;
296
- }
183
+ try {
184
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
185
+ const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
186
+ if (actions?.length) {
187
+ const capturedToolResults = Array.isArray(chat.toolOutputs)
188
+ ? chat.toolOutputs.map((entry) => ({
189
+ tool_call_id: entry.tool_call_id,
190
+ output: entry.content,
191
+ name: entry.name
192
+ }))
193
+ : undefined;
194
+ const actionState = createBridgeActionState({
195
+ messages: Array.isArray(payload.messages) ? payload.messages : undefined,
196
+ rawRequest: payload,
197
+ metadata: chat.metadata,
198
+ capturedToolResults
199
+ });
200
+ runBridgeActionPipeline({
201
+ stage: 'request_outbound',
202
+ actions,
203
+ protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
204
+ moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
205
+ requestId: ctx.requestId,
206
+ state: actionState
207
+ });
297
208
  }
298
209
  }
210
+ catch {
211
+ // ignore metadata propagation failures
212
+ }
299
213
  return {
300
214
  protocol: 'anthropic-messages',
301
215
  direction: 'response',
@@ -1,4 +1,7 @@
1
1
  import { isJsonObject, jsonClone } from '../types/json.js';
2
+ import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
3
+ import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
4
+ import { normalizeChatMessageContent } from '../../shared/chat-output-normalizer.js';
2
5
  const CHAT_PARAMETER_KEYS = [
3
6
  'model',
4
7
  'temperature',
@@ -115,6 +118,15 @@ function normalizeChatMessages(raw) {
115
118
  return;
116
119
  }
117
120
  const chatMessage = value;
121
+ if (roleValue !== 'system' && roleValue !== 'tool') {
122
+ const normalizedContent = normalizeChatMessageContent(chatMessage.content);
123
+ if (normalizedContent.contentText !== undefined) {
124
+ chatMessage.content = normalizedContent.contentText;
125
+ }
126
+ if (typeof normalizedContent.reasoningText === 'string' && normalizedContent.reasoningText.trim().length) {
127
+ chatMessage.reasoning_content = normalizedContent.reasoningText.trim();
128
+ }
129
+ }
118
130
  norm.messages.push(chatMessage);
119
131
  if (roleValue === 'system') {
120
132
  const segment = flattenSystemContent(chatMessage.content);
@@ -185,27 +197,10 @@ function extractParameters(body) {
185
197
  }
186
198
  return Object.keys(params).length ? params : undefined;
187
199
  }
188
- function collectExtraFields(payload) {
189
- const extras = {};
190
- for (const key of Object.keys(payload)) {
191
- if (KNOWN_TOP_LEVEL_FIELDS.has(key))
192
- continue;
193
- extras[key] = payload[key];
194
- }
195
- return Object.keys(extras).length ? extras : undefined;
196
- }
197
200
  export class ChatSemanticMapper {
198
201
  async toChat(format, ctx) {
199
202
  const payload = (format.payload ?? {});
200
203
  const normalized = normalizeChatMessages(payload.messages);
201
- const rawSystemSentinel = payload.__rcc_raw_system;
202
- const providerMetadataSentinel = payload.__rcc_provider_metadata;
203
- if (rawSystemSentinel !== undefined) {
204
- delete payload.__rcc_raw_system;
205
- }
206
- if (providerMetadataSentinel !== undefined) {
207
- delete payload.__rcc_provider_metadata;
208
- }
209
204
  const topLevelOutputs = normalizeStandaloneToolOutputs(payload.tool_outputs, normalized.missingFields);
210
205
  const toolOutputs = [...normalized.toolOutputs];
211
206
  for (const entry of topLevelOutputs) {
@@ -214,22 +209,6 @@ export class ChatSemanticMapper {
214
209
  }
215
210
  }
216
211
  const metadata = { context: ctx };
217
- if (typeof rawSystemSentinel === 'string') {
218
- try {
219
- metadata.rawSystem = JSON.parse(rawSystemSentinel);
220
- }
221
- catch {
222
- // ignore parse errors
223
- }
224
- }
225
- if (typeof providerMetadataSentinel === 'string') {
226
- try {
227
- metadata.providerMetadata = JSON.parse(providerMetadataSentinel);
228
- }
229
- catch {
230
- // ignore parse errors
231
- }
232
- }
233
212
  if (normalized.systemSegments.length) {
234
213
  metadata.systemInstructions = normalized.systemSegments;
235
214
  }
@@ -240,9 +219,27 @@ export class ChatSemanticMapper {
240
219
  if (normalized.missingFields.length) {
241
220
  metadata.missingFields = normalized.missingFields;
242
221
  }
243
- const extraFields = collectExtraFields(payload);
244
- if (extraFields) {
245
- metadata.extraFields = extraFields;
222
+ try {
223
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-chat' });
224
+ const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
225
+ if (actions?.length) {
226
+ const actionState = createBridgeActionState({
227
+ messages: normalized.messages,
228
+ rawRequest: payload,
229
+ metadata: metadata
230
+ });
231
+ runBridgeActionPipeline({
232
+ stage: 'request_inbound',
233
+ actions,
234
+ protocol: bridgePolicy?.protocol ?? 'openai-chat',
235
+ moduleType: bridgePolicy?.moduleType ?? 'openai-chat',
236
+ requestId: ctx.requestId,
237
+ state: actionState
238
+ });
239
+ }
240
+ }
241
+ catch {
242
+ // noop: best-effort policy application
246
243
  }
247
244
  if (Array.isArray(payload.tools) && payload.tools.length === 0) {
248
245
  metadata.toolsFieldPresent = true;
@@ -261,17 +258,39 @@ export class ChatSemanticMapper {
261
258
  tools: chat.tools ?? (chat.metadata?.toolsFieldPresent ? [] : undefined),
262
259
  ...(chat.parameters || {})
263
260
  };
264
- if (chat.metadata?.rawSystem !== undefined) {
265
- try {
266
- payload.__rcc_raw_system = JSON.stringify(chat.metadata.rawSystem);
267
- }
268
- catch {
269
- // ignore serialization errors
261
+ try {
262
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-chat' });
263
+ const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
264
+ if (actions?.length && Array.isArray(payload.messages)) {
265
+ const capturedToolResults = Array.isArray(chat.toolOutputs)
266
+ ? chat.toolOutputs.map((entry) => ({
267
+ tool_call_id: entry.tool_call_id,
268
+ output: entry.content,
269
+ name: entry.name
270
+ }))
271
+ : undefined;
272
+ const actionState = createBridgeActionState({
273
+ messages: payload.messages,
274
+ rawRequest: payload,
275
+ metadata: chat.metadata,
276
+ capturedToolResults
277
+ });
278
+ runBridgeActionPipeline({
279
+ stage: 'request_outbound',
280
+ actions,
281
+ protocol: bridgePolicy?.protocol ?? 'openai-chat',
282
+ moduleType: bridgePolicy?.moduleType ?? 'openai-chat',
283
+ requestId: ctx.requestId,
284
+ state: actionState
285
+ });
270
286
  }
271
287
  }
272
- if (chat.metadata?.providerMetadata && isJsonObject(chat.metadata.providerMetadata)) {
288
+ catch {
289
+ // ignore policy failures
290
+ }
291
+ if (chat.metadata?.rawSystem !== undefined) {
273
292
  try {
274
- payload.__rcc_provider_metadata = JSON.stringify(chat.metadata.providerMetadata);
293
+ payload.__rcc_raw_system = JSON.stringify(chat.metadata.rawSystem);
275
294
  }
276
295
  catch {
277
296
  // ignore serialization errors
@@ -284,14 +303,6 @@ export class ChatSemanticMapper {
284
303
  payload.max_tokens = payload.max_output_tokens;
285
304
  delete payload.max_output_tokens;
286
305
  }
287
- const extraFields = chat.metadata?.extraFields;
288
- if (isJsonObject(extraFields)) {
289
- for (const [key, value] of Object.entries(extraFields)) {
290
- if (payload[key] === undefined) {
291
- payload[key] = value;
292
- }
293
- }
294
- }
295
306
  return {
296
307
  protocol: 'openai-chat',
297
308
  direction: 'response',