@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
@@ -1,5 +1,10 @@
1
1
  import { isJsonObject, jsonClone } from '../types/json.js';
2
2
  import { buildOpenAIChatFromGeminiRequest } from '../../codecs/gemini-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 { mapBridgeToolsToChat, mapChatToolsToBridge } from '../../shared/tool-mapping.js';
7
+ import { prepareGeminiToolsForBridge, buildGeminiToolsFromBridge } from '../../shared/gemini-tool-utils.js';
3
8
  const GENERATION_CONFIG_KEYS = [
4
9
  { source: 'temperature', target: 'temperature' },
5
10
  { source: 'topP', target: 'top_p' },
@@ -9,65 +14,8 @@ const GENERATION_CONFIG_KEYS = [
9
14
  { source: 'responseMimeType', target: 'response_mime_type' },
10
15
  { source: 'stopSequences', target: 'stop_sequences' }
11
16
  ];
12
- const GEMINI_TOP_LEVEL_FIELDS = new Set([
13
- 'model',
14
- 'contents',
15
- 'tools',
16
- 'systemInstruction',
17
- 'generationConfig',
18
- 'safetySettings',
19
- 'metadata',
20
- 'toolConfig'
21
- ]);
22
17
  const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
23
18
  const PASSTHROUGH_PARAMETERS = ['tool_choice'];
24
- function encodePassthroughParametersFromChat(chat) {
25
- if (!chat.parameters)
26
- return undefined;
27
- const encoded = {};
28
- for (const key of PASSTHROUGH_PARAMETERS) {
29
- const value = chat.parameters[key];
30
- if (value === undefined)
31
- continue;
32
- try {
33
- encoded[`${PASSTHROUGH_METADATA_PREFIX}${key}`] = JSON.stringify(value);
34
- }
35
- catch {
36
- continue;
37
- }
38
- }
39
- return Object.keys(encoded).length ? encoded : undefined;
40
- }
41
- function extractPassthroughParametersFromMetadata(metadataField) {
42
- if (!metadataField)
43
- return {};
44
- const cloned = jsonClone(metadataField);
45
- const passthrough = {};
46
- let mutated = false;
47
- for (const key of Object.keys(cloned)) {
48
- if (!key.startsWith(PASSTHROUGH_METADATA_PREFIX))
49
- continue;
50
- const paramKey = key.slice(PASSTHROUGH_METADATA_PREFIX.length);
51
- if (!PASSTHROUGH_PARAMETERS.includes(paramKey))
52
- continue;
53
- const rawValue = cloned[key];
54
- if (typeof rawValue !== 'string')
55
- continue;
56
- try {
57
- const parsed = rawValue ? JSON.parse(rawValue) : undefined;
58
- passthrough[paramKey] = parsed;
59
- delete cloned[key];
60
- mutated = true;
61
- }
62
- catch {
63
- continue;
64
- }
65
- }
66
- return {
67
- cleaned: mutated ? (Object.keys(cloned).length ? cloned : undefined) : cloned,
68
- passthrough: Object.keys(passthrough).length ? passthrough : undefined
69
- };
70
- }
71
19
  function normalizeToolOutputs(messages, missing) {
72
20
  const outputs = [];
73
21
  messages.forEach((msg, index) => {
@@ -119,53 +67,6 @@ function collectSystemSegments(systemInstruction) {
119
67
  const text = flatten(systemInstruction).trim();
120
68
  return text ? [text] : [];
121
69
  }
122
- function normalizeGeminiTools(rawTools, missing) {
123
- if (!rawTools)
124
- return undefined;
125
- const arr = Array.isArray(rawTools) ? rawTools : [rawTools];
126
- const tools = [];
127
- arr.forEach((entry, index) => {
128
- if (!entry || typeof entry !== 'object') {
129
- missing.push({ path: `tools[${index}]`, reason: 'invalid_entry', originalValue: jsonClone(entry) });
130
- return;
131
- }
132
- const obj = entry;
133
- const declarations = Array.isArray(obj.functionDeclarations) ? obj.functionDeclarations : undefined;
134
- if (declarations && declarations.length) {
135
- declarations.forEach((decl, declIndex) => {
136
- if (!decl || typeof decl !== 'object') {
137
- missing.push({ path: `tools[${index}].functionDeclarations[${declIndex}]`, reason: 'invalid_entry' });
138
- return;
139
- }
140
- const def = decl;
141
- const name = typeof def.name === 'string' ? def.name : undefined;
142
- if (!name)
143
- return;
144
- tools.push({
145
- type: 'function',
146
- function: {
147
- name,
148
- description: typeof def.description === 'string' ? def.description : undefined,
149
- parameters: isJsonObject(def.parameters) ? jsonClone(def.parameters) : { type: 'object', properties: {} }
150
- }
151
- });
152
- });
153
- return;
154
- }
155
- const name = typeof obj.name === 'string' ? obj.name : undefined;
156
- if (!name)
157
- return;
158
- tools.push({
159
- type: 'function',
160
- function: {
161
- name,
162
- description: typeof obj.description === 'string' ? obj.description : undefined,
163
- parameters: isJsonObject(obj.parameters) ? jsonClone(obj.parameters) : { type: 'object', properties: {} }
164
- }
165
- });
166
- });
167
- return tools.length ? tools : undefined;
168
- }
169
70
  function collectParameters(payload) {
170
71
  const params = {};
171
72
  if (typeof payload.model === 'string') {
@@ -189,15 +90,6 @@ function collectParameters(payload) {
189
90
  }
190
91
  return Object.keys(params).length ? params : undefined;
191
92
  }
192
- function collectExtraFields(payload) {
193
- const extras = {};
194
- for (const key of Object.keys(payload)) {
195
- if (GEMINI_TOP_LEVEL_FIELDS.has(key))
196
- continue;
197
- extras[key] = payload[key];
198
- }
199
- return Object.keys(extras).length ? extras : undefined;
200
- }
201
93
  function buildGeminiRequestFromChat(chat, metadata) {
202
94
  const contents = [];
203
95
  for (const message of chat.messages) {
@@ -276,7 +168,11 @@ function buildGeminiRequestFromChat(chat, metadata) {
276
168
  }
277
169
  }
278
170
  if (chat.tools && chat.tools.length) {
279
- request.tools = buildGeminiToolsFromChat(chat.tools);
171
+ const bridgeDefs = mapChatToolsToBridge(chat.tools);
172
+ const geminiTools = buildGeminiToolsFromBridge(bridgeDefs);
173
+ if (geminiTools) {
174
+ request.tools = geminiTools;
175
+ }
280
176
  }
281
177
  const generationConfig = buildGenerationConfigFromParameters(chat.parameters || {});
282
178
  if (Object.keys(generationConfig).length) {
@@ -309,7 +205,10 @@ function buildGeminiRequestFromChat(chat, metadata) {
309
205
  request.metadata = request.metadata ?? {};
310
206
  request.metadata.__rcc_tools_field_present = '1';
311
207
  }
312
- const passthrough = encodePassthroughParametersFromChat(chat);
208
+ const passthrough = encodeMetadataPassthrough(chat.parameters, {
209
+ prefix: PASSTHROUGH_METADATA_PREFIX,
210
+ keys: PASSTHROUGH_PARAMETERS
211
+ });
313
212
  if (passthrough) {
314
213
  request.metadata = request.metadata ?? {};
315
214
  for (const [key, value] of Object.entries(passthrough)) {
@@ -318,26 +217,6 @@ function buildGeminiRequestFromChat(chat, metadata) {
318
217
  }
319
218
  return request;
320
219
  }
321
- function buildGeminiToolsFromChat(tools) {
322
- const geminiTools = [];
323
- tools.forEach((tool) => {
324
- if (!tool || tool.type !== 'function')
325
- return;
326
- const fn = tool.function;
327
- if (!fn || typeof fn.name !== 'string')
328
- return;
329
- geminiTools.push({
330
- functionDeclarations: [
331
- {
332
- name: fn.name,
333
- description: fn.description,
334
- parameters: cloneAsJsonValue(fn.parameters ?? { type: 'object', properties: {} })
335
- }
336
- ]
337
- });
338
- });
339
- return geminiTools;
340
- }
341
220
  function buildGenerationConfigFromParameters(parameters) {
342
221
  const config = {};
343
222
  for (const { source, target } of GENERATION_CONFIG_KEYS) {
@@ -391,12 +270,13 @@ export class GeminiSemanticMapper {
391
270
  async toChat(format, ctx) {
392
271
  const payload = (format.payload ?? {});
393
272
  const missing = [];
394
- const { messages } = buildOpenAIChatFromGeminiRequest(payload);
273
+ const { messages: builtMessages } = buildOpenAIChatFromGeminiRequest(payload);
274
+ let messages = Array.isArray(builtMessages) ? builtMessages : [];
395
275
  if (!Array.isArray(payload.contents)) {
396
276
  missing.push({ path: 'contents', reason: 'absent' });
397
277
  }
398
- const toolOutputs = normalizeToolOutputs(messages, missing);
399
- const tools = payload.tools ? normalizeGeminiTools(payload.tools, missing) : undefined;
278
+ const bridgeTools = prepareGeminiToolsForBridge(payload.tools, missing);
279
+ const tools = bridgeTools ? mapBridgeToolsToChat(bridgeTools) : undefined;
400
280
  let parameters = collectParameters(payload);
401
281
  const metadata = { context: ctx };
402
282
  const systemSegments = collectSystemSegments(payload.systemInstruction);
@@ -414,15 +294,38 @@ export class GeminiSemanticMapper {
414
294
  if (missing.length) {
415
295
  metadata.missingFields = missing;
416
296
  }
417
- const extraFields = collectExtraFields(payload);
418
- if (extraFields) {
419
- metadata.extraFields = extraFields;
297
+ try {
298
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'gemini-chat' });
299
+ const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
300
+ if (actions?.length) {
301
+ const actionState = createBridgeActionState({
302
+ messages: messages,
303
+ rawRequest: payload,
304
+ metadata: metadata
305
+ });
306
+ runBridgeActionPipeline({
307
+ stage: 'request_inbound',
308
+ actions,
309
+ protocol: bridgePolicy?.protocol ?? 'gemini-chat',
310
+ moduleType: bridgePolicy?.moduleType ?? 'gemini-chat',
311
+ requestId: ctx.requestId,
312
+ state: actionState
313
+ });
314
+ messages = actionState.messages;
315
+ }
420
316
  }
421
- const passthrough = extractPassthroughParametersFromMetadata(payload.metadata);
317
+ catch {
318
+ // best-effort policy execution
319
+ }
320
+ const toolOutputs = normalizeToolOutputs(messages, missing);
321
+ const passthrough = extractMetadataPassthrough(payload.metadata, {
322
+ prefix: PASSTHROUGH_METADATA_PREFIX,
323
+ keys: PASSTHROUGH_PARAMETERS
324
+ });
422
325
  if (passthrough.passthrough) {
423
326
  parameters = { ...(parameters || {}), ...passthrough.passthrough };
424
327
  }
425
- const providerMetadataSource = passthrough.cleaned ?? payload.metadata;
328
+ const providerMetadataSource = passthrough.metadata ?? payload.metadata;
426
329
  if (providerMetadataSource) {
427
330
  const providerMetadata = jsonClone(providerMetadataSource);
428
331
  let toolsFieldPresent = false;
@@ -465,6 +368,36 @@ export class GeminiSemanticMapper {
465
368
  }
466
369
  async fromChat(chat, ctx) {
467
370
  const envelopePayload = buildGeminiRequestFromChat(chat, chat.metadata);
371
+ try {
372
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'gemini-chat' });
373
+ const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
374
+ if (actions?.length) {
375
+ const capturedToolResults = Array.isArray(chat.toolOutputs)
376
+ ? chat.toolOutputs.map((entry) => ({
377
+ tool_call_id: entry.tool_call_id,
378
+ output: entry.content,
379
+ name: entry.name
380
+ }))
381
+ : undefined;
382
+ const actionState = createBridgeActionState({
383
+ messages: Array.isArray(chat.messages) ? chat.messages : [],
384
+ rawRequest: envelopePayload,
385
+ metadata: chat.metadata,
386
+ capturedToolResults
387
+ });
388
+ runBridgeActionPipeline({
389
+ stage: 'request_outbound',
390
+ actions,
391
+ protocol: bridgePolicy?.protocol ?? 'gemini-chat',
392
+ moduleType: bridgePolicy?.moduleType ?? 'gemini-chat',
393
+ requestId: ctx.requestId,
394
+ state: actionState
395
+ });
396
+ }
397
+ }
398
+ catch {
399
+ // ignore policy failures
400
+ }
468
401
  return {
469
402
  protocol: 'gemini-chat',
470
403
  direction: 'response',
@@ -1,4 +1,6 @@
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';
2
4
  import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesRequestFromChat } from '../../responses/responses-openai-bridge.js';
3
5
  const RESPONSES_PARAMETER_KEYS = [
4
6
  'model',
@@ -20,34 +22,6 @@ const RESPONSES_PARAMETER_KEYS = [
20
22
  'stop_sequences',
21
23
  'modalities'
22
24
  ];
23
- const RESPONSES_TOP_LEVEL_FIELDS = new Set([
24
- 'input',
25
- 'tools',
26
- 'tool_outputs',
27
- 'instructions',
28
- 'response_id',
29
- 'previous_response_id',
30
- 'conversation',
31
- 'parallel_tool_calls',
32
- 'tool_choice',
33
- 'stream',
34
- 'metadata',
35
- 'include',
36
- 'store',
37
- 'user',
38
- 'response_format',
39
- 'model',
40
- 'temperature',
41
- 'top_p',
42
- 'top_k',
43
- 'max_tokens',
44
- 'max_output_tokens',
45
- 'logit_bias',
46
- 'seed',
47
- 'modalities',
48
- 'instructions_is_raw'
49
- ]);
50
- const RESPONSES_METADATA_SENTINEL_TOOLS = '__rcc_tools_field_present';
51
25
  function mapToolOutputs(entries, missing) {
52
26
  if (!entries || !entries.length)
53
27
  return undefined;
@@ -146,15 +120,6 @@ function normalizeMessages(value, missing) {
146
120
  });
147
121
  return messages;
148
122
  }
149
- function collectExtraFields(payload) {
150
- const extras = {};
151
- for (const key of Object.keys(payload)) {
152
- if (RESPONSES_TOP_LEVEL_FIELDS.has(key) || RESPONSES_PARAMETER_KEYS.includes(key))
153
- continue;
154
- extras[key] = payload[key];
155
- }
156
- return Object.keys(extras).length ? extras : undefined;
157
- }
158
123
  function serializeSystemContent(message) {
159
124
  if (!message)
160
125
  return undefined;
@@ -192,40 +157,37 @@ export class ResponsesSemanticMapper {
192
157
  const responsesContext = captureResponsesContext(payload, { route: { requestId: ctx.requestId } });
193
158
  const { request, toolsNormalized } = buildChatRequestFromResponses(payload, responsesContext);
194
159
  const missingFields = [];
195
- if (!responsesContext.instructions) {
196
- missingFields.push({ path: 'instructions', reason: 'absent' });
197
- }
198
160
  const messages = normalizeMessages(request.messages, missingFields);
199
161
  const toolOutputs = mapToolOutputs(payload.tool_outputs, missingFields);
200
162
  const parameters = collectParameters(payload, responsesContext.stream);
201
163
  const metadata = { context: ctx };
202
- if (payload.metadata && typeof payload.metadata === 'object') {
203
- const providerMetadata = jsonClone(payload.metadata);
204
- if (isJsonObject(providerMetadata)) {
205
- if (Object.prototype.hasOwnProperty.call(providerMetadata, RESPONSES_METADATA_SENTINEL_TOOLS)) {
206
- const sentinel = providerMetadata[RESPONSES_METADATA_SENTINEL_TOOLS];
207
- if (sentinel === '1' || sentinel === true) {
208
- metadata.toolsFieldPresent = true;
209
- }
210
- delete providerMetadata[RESPONSES_METADATA_SENTINEL_TOOLS];
211
- }
164
+ try {
165
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
166
+ const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
167
+ if (actions?.length) {
168
+ const actionState = createBridgeActionState({
169
+ rawRequest: payload,
170
+ metadata: metadata
171
+ });
172
+ runBridgeActionPipeline({
173
+ stage: 'request_inbound',
174
+ actions,
175
+ protocol: bridgePolicy?.protocol ?? 'openai-responses',
176
+ moduleType: bridgePolicy?.moduleType ?? 'openai-responses',
177
+ requestId: ctx.requestId,
178
+ state: actionState
179
+ });
212
180
  }
213
- metadata.providerMetadata = providerMetadata;
181
+ }
182
+ catch {
183
+ // ignore policy hook failures
214
184
  }
215
185
  if (missingFields.length) {
216
186
  metadata.missingFields = missingFields;
217
187
  }
218
- const extraFields = collectExtraFields(payload);
219
- if (extraFields) {
220
- metadata.extraFields = extraFields;
221
- }
222
188
  if (responsesContext.responseFormat) {
223
189
  metadata.responseFormat = jsonClone(responsesContext.responseFormat);
224
190
  }
225
- const hasSystemMessage = messages.some(message => message?.role === 'system');
226
- if (!hasSystemMessage && typeof responsesContext.instructions === 'string' && responsesContext.instructions.length) {
227
- messages.unshift({ role: 'system', content: responsesContext.instructions });
228
- }
229
191
  return {
230
192
  messages,
231
193
  tools: normalizeTools(toolsNormalized, missingFields),
@@ -254,100 +216,6 @@ export class ResponsesSemanticMapper {
254
216
  : { originalSystemMessages };
255
217
  const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
256
218
  const responses = responsesResult.request;
257
- const normalizeUserInstructionFromMessage = (message) => {
258
- if (!message)
259
- return undefined;
260
- const content = message.content;
261
- if (typeof content === 'string') {
262
- const trimmed = content.trim();
263
- return trimmed.length ? trimmed : undefined;
264
- }
265
- if (Array.isArray(content)) {
266
- const fragments = [];
267
- for (const fragment of content) {
268
- if (typeof fragment === 'string') {
269
- fragments.push(fragment);
270
- }
271
- else if (fragment && typeof fragment === 'object') {
272
- const partText = typeof fragment.text === 'string'
273
- ? String(fragment.text)
274
- : (typeof fragment.content === 'string'
275
- ? String(fragment.content)
276
- : undefined);
277
- if (partText && partText.trim().length) {
278
- fragments.push(partText);
279
- }
280
- }
281
- }
282
- if (fragments.length) {
283
- const joined = fragments.join('').trim();
284
- return joined.length ? joined : undefined;
285
- }
286
- }
287
- return undefined;
288
- };
289
- const normalizeUserInstructionFromInput = (payload) => {
290
- const entries = Array.isArray(payload.input) ? payload.input : [];
291
- for (let i = entries.length - 1; i >= 0; i -= 1) {
292
- const entry = entries[i];
293
- const roleSource = entry.role ?? entry?.message?.role;
294
- const role = typeof roleSource === 'string' ? roleSource.toLowerCase() : '';
295
- if (role !== 'user')
296
- continue;
297
- const content = entry?.content ?? entry?.message?.content;
298
- if (typeof content === 'string') {
299
- const trimmed = content.trim();
300
- if (trimmed.length)
301
- return trimmed;
302
- continue;
303
- }
304
- if (Array.isArray(content)) {
305
- const parts = [];
306
- for (const fragment of content) {
307
- if (typeof fragment === 'string') {
308
- parts.push(fragment);
309
- }
310
- else if (fragment && typeof fragment === 'object') {
311
- const text = typeof fragment.text === 'string'
312
- ? String(fragment.text)
313
- : (typeof fragment.content === 'string'
314
- ? String(fragment.content)
315
- : undefined);
316
- if (text && text.trim().length) {
317
- parts.push(text);
318
- }
319
- }
320
- }
321
- if (parts.length) {
322
- const joined = parts.join('').trim();
323
- if (joined.length)
324
- return joined;
325
- }
326
- }
327
- }
328
- return undefined;
329
- };
330
- const latestUserMessage = [...(chat.messages || [])]
331
- .reverse()
332
- .find((message) => typeof message?.role === 'string' && message.role.toLowerCase() === 'user');
333
- const existingInstruction = typeof responses.instructions === 'string'
334
- ? responses.instructions.trim()
335
- : undefined;
336
- if (!existingInstruction || !existingInstruction.length) {
337
- const messageInstruction = normalizeUserInstructionFromMessage(latestUserMessage);
338
- if (messageInstruction && messageInstruction.trim().length) {
339
- responses.instructions = messageInstruction.trim();
340
- }
341
- else {
342
- const inputInstruction = normalizeUserInstructionFromInput(responses);
343
- if (inputInstruction && inputInstruction.trim().length) {
344
- responses.instructions = inputInstruction.trim();
345
- }
346
- }
347
- }
348
- else {
349
- responses.instructions = existingInstruction;
350
- }
351
219
  if (chat.parameters && chat.parameters.stream !== undefined) {
352
220
  responses.stream = chat.parameters.stream;
353
221
  }
@@ -358,15 +226,27 @@ export class ResponsesSemanticMapper {
358
226
  name: entry.name
359
227
  }));
360
228
  }
361
- // Responses outbound 不再附带 metadata;系统提示由 instructions 字段承担
362
- const extraFields = chat.metadata?.extraFields;
363
- if (isJsonObject(extraFields)) {
364
- for (const [key, value] of Object.entries(extraFields)) {
365
- if (responses[key] === undefined) {
366
- responses[key] = value;
367
- }
229
+ try {
230
+ const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
231
+ const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
232
+ if (actions?.length) {
233
+ const actionState = createBridgeActionState({
234
+ rawRequest: responses,
235
+ metadata: chat.metadata
236
+ });
237
+ runBridgeActionPipeline({
238
+ stage: 'request_outbound',
239
+ actions,
240
+ protocol: bridgePolicy?.protocol ?? 'openai-responses',
241
+ moduleType: bridgePolicy?.moduleType ?? 'openai-responses',
242
+ requestId: ctx.requestId,
243
+ state: actionState
244
+ });
368
245
  }
369
246
  }
247
+ catch {
248
+ // ignore metadata propagation failures
249
+ }
370
250
  return {
371
251
  protocol: 'openai-responses',
372
252
  direction: 'response',
@@ -56,6 +56,9 @@ export function standardizedToChatEnvelope(request, options) {
56
56
  const metadata = {
57
57
  context: adapterContext
58
58
  };
59
+ if (typeof adapterContext.toolCallIdStyle === 'string' && adapterContext.toolCallIdStyle.length) {
60
+ metadata.toolCallIdStyle = adapterContext.toolCallIdStyle;
61
+ }
59
62
  if (hubState?.missingFields) {
60
63
  metadata.missingFields = hubState.missingFields;
61
64
  }
@@ -1,5 +1,5 @@
1
- const ALPHA_NUMERIC = /[A-Za-z0-9._-]/;
2
- const LOWER_SNAKE = /[a-z0-9._-]/;
1
+ const ALPHA_NUMERIC = /[A-Za-z0-9_-]/;
2
+ const LOWER_SNAKE = /[a-z0-9_-]/;
3
3
  function clonePattern(pattern) {
4
4
  if (!pattern)
5
5
  return undefined;
@@ -27,3 +27,8 @@ export { governTools } from './shared/tool-governor.js';
27
27
  export * from './shared/streaming-text-extractor.js';
28
28
  export * from './shared/tool-harvester.js';
29
29
  export * from './responses/responses-openai-bridge.js';
30
+ export { ProtocolConversionPipeline, PipelineValidationError, type ProtocolInboundPipelineOptions, type ProtocolInboundPipelineResult, type ProtocolOutboundPipelineOptions, type ProtocolOutboundPipelineResult } from './pipeline/index.js';
31
+ export * from './pipeline/schema/index.js';
32
+ export * from './pipeline/hooks/protocol-hooks.js';
33
+ export * from './pipeline/hooks/adapter-context.js';
34
+ export * from './pipeline/meta/meta-bag.js';
@@ -27,3 +27,8 @@ export { governTools } from './shared/tool-governor.js';
27
27
  export * from './shared/streaming-text-extractor.js';
28
28
  export * from './shared/tool-harvester.js';
29
29
  export * from './responses/responses-openai-bridge.js';
30
+ export { ProtocolConversionPipeline, PipelineValidationError } from './pipeline/index.js';
31
+ export * from './pipeline/schema/index.js';
32
+ export * from './pipeline/hooks/protocol-hooks.js';
33
+ export * from './pipeline/hooks/adapter-context.js';
34
+ export * from './pipeline/meta/meta-bag.js';
@@ -0,0 +1,12 @@
1
+ import type { ConversionCodec, ConversionContext, ConversionProfile } from '../../../types.js';
2
+ import type { JsonObject } from '../../../hub/types/json.js';
3
+ export declare class AnthropicOpenAIPipelineCodec implements ConversionCodec {
4
+ readonly id = "anthropic-openai-v2";
5
+ private readonly pipeline;
6
+ private initialized;
7
+ constructor();
8
+ initialize(): Promise<void>;
9
+ private ensureInitialized;
10
+ convertRequest(payload: unknown, profile: ConversionProfile, context: ConversionContext): Promise<JsonObject>;
11
+ convertResponse(payload: unknown, profile: ConversionProfile, context: ConversionContext): Promise<JsonObject>;
12
+ }