@jsonstudio/llms 0.4.6 → 0.6.2

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 (99) hide show
  1. package/dist/conversion/codecs/anthropic-openai-codec.js +28 -2
  2. package/dist/conversion/codecs/gemini-openai-codec.js +23 -0
  3. package/dist/conversion/codecs/responses-openai-codec.js +8 -1
  4. package/dist/conversion/hub/node-support.js +14 -1
  5. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +66 -0
  6. package/dist/conversion/hub/pipeline/hub-pipeline.js +284 -193
  7. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +11 -0
  8. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +6 -0
  9. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +16 -0
  10. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +17 -0
  11. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.d.ts +5 -0
  12. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +17 -0
  13. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.d.ts +19 -0
  14. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +269 -0
  15. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +18 -0
  16. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +141 -0
  17. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +11 -0
  18. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +29 -0
  19. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +16 -0
  20. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +15 -0
  21. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +17 -0
  22. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +18 -0
  23. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.d.ts +17 -0
  24. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +63 -0
  25. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.d.ts +11 -0
  26. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +6 -0
  27. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +12 -0
  28. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +6 -0
  29. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +13 -0
  30. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +43 -0
  31. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.d.ts +17 -0
  32. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +22 -0
  33. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.d.ts +16 -0
  34. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +19 -0
  35. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.d.ts +17 -0
  36. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +19 -0
  37. package/dist/conversion/hub/pipeline/stages/utils.d.ts +2 -0
  38. package/dist/conversion/hub/pipeline/stages/utils.js +11 -0
  39. package/dist/conversion/hub/pipeline/target-utils.d.ts +5 -0
  40. package/dist/conversion/hub/pipeline/target-utils.js +87 -0
  41. package/dist/conversion/hub/process/chat-process.js +23 -17
  42. package/dist/conversion/hub/response/provider-response.js +69 -122
  43. package/dist/conversion/hub/response/response-mappers.d.ts +19 -0
  44. package/dist/conversion/hub/response/response-mappers.js +22 -2
  45. package/dist/conversion/hub/response/response-runtime.d.ts +8 -0
  46. package/dist/conversion/hub/response/response-runtime.js +239 -6
  47. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +8 -0
  48. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +135 -55
  49. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +80 -40
  50. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +5 -29
  51. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +16 -13
  52. package/dist/conversion/hub/snapshot-recorder.d.ts +13 -0
  53. package/dist/conversion/hub/snapshot-recorder.js +90 -50
  54. package/dist/conversion/hub/standardized-bridge.js +49 -38
  55. package/dist/conversion/hub/types/chat-envelope.d.ts +68 -0
  56. package/dist/conversion/hub/types/standardized.d.ts +97 -0
  57. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +29 -2
  58. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +68 -1
  59. package/dist/conversion/responses/responses-openai-bridge.d.ts +6 -1
  60. package/dist/conversion/responses/responses-openai-bridge.js +132 -10
  61. package/dist/conversion/shared/anthropic-message-utils.d.ts +9 -1
  62. package/dist/conversion/shared/anthropic-message-utils.js +414 -26
  63. package/dist/conversion/shared/bridge-actions.js +267 -95
  64. package/dist/conversion/shared/bridge-message-utils.js +54 -8
  65. package/dist/conversion/shared/bridge-policies.js +21 -2
  66. package/dist/conversion/shared/chat-envelope-validator.d.ts +8 -0
  67. package/dist/conversion/shared/chat-envelope-validator.js +128 -0
  68. package/dist/conversion/shared/chat-request-filters.js +109 -28
  69. package/dist/conversion/shared/mcp-injection.js +41 -20
  70. package/dist/conversion/shared/openai-finalizer.d.ts +11 -0
  71. package/dist/conversion/shared/openai-finalizer.js +73 -0
  72. package/dist/conversion/shared/openai-message-normalize.js +32 -31
  73. package/dist/conversion/shared/protocol-state.d.ts +4 -0
  74. package/dist/conversion/shared/protocol-state.js +23 -0
  75. package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
  76. package/dist/conversion/shared/reasoning-normalizer.js +50 -18
  77. package/dist/conversion/shared/responses-output-builder.d.ts +1 -1
  78. package/dist/conversion/shared/responses-output-builder.js +76 -25
  79. package/dist/conversion/shared/responses-reasoning-registry.d.ts +8 -0
  80. package/dist/conversion/shared/responses-reasoning-registry.js +61 -0
  81. package/dist/conversion/shared/responses-response-utils.js +32 -2
  82. package/dist/conversion/shared/responses-tool-utils.js +28 -2
  83. package/dist/conversion/shared/snapshot-hooks.d.ts +9 -0
  84. package/dist/conversion/shared/snapshot-hooks.js +60 -6
  85. package/dist/conversion/shared/snapshot-utils.d.ts +16 -0
  86. package/dist/conversion/shared/snapshot-utils.js +84 -0
  87. package/dist/conversion/shared/tool-filter-pipeline.js +46 -7
  88. package/dist/conversion/shared/tool-mapping.js +13 -2
  89. package/dist/filters/index.d.ts +18 -0
  90. package/dist/filters/index.js +0 -1
  91. package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +13 -0
  92. package/dist/filters/special/request-streaming-to-nonstreaming.js +13 -1
  93. package/dist/filters/special/request-tool-choice-policy.js +3 -1
  94. package/dist/filters/special/request-tool-list-filter.d.ts +11 -0
  95. package/dist/filters/special/request-tool-list-filter.js +20 -7
  96. package/dist/sse/shared/responses-output-normalizer.js +5 -4
  97. package/dist/sse/sse-to-json/builders/response-builder.js +24 -1
  98. package/dist/sse/types/responses-types.d.ts +2 -0
  99. package/package.json +1 -1
@@ -32,6 +32,85 @@ function flattenAnthropicText(content) {
32
32
  }
33
33
  return '';
34
34
  }
35
+ function normalizeToolResultContent(block) {
36
+ if (!block || typeof block !== 'object') {
37
+ return '';
38
+ }
39
+ const content = block.content;
40
+ if (typeof content === 'string') {
41
+ return content;
42
+ }
43
+ if (Array.isArray(content)) {
44
+ const segments = [];
45
+ for (const entry of content) {
46
+ const segment = extractToolResultSegment(entry);
47
+ if (segment) {
48
+ segments.push(segment);
49
+ }
50
+ }
51
+ if (segments.length) {
52
+ return segments.join('\n');
53
+ }
54
+ }
55
+ else if (content != null) {
56
+ try {
57
+ return JSON.stringify(content);
58
+ }
59
+ catch {
60
+ return String(content);
61
+ }
62
+ }
63
+ return '';
64
+ }
65
+ function extractToolResultSegment(entry) {
66
+ if (entry == null) {
67
+ return '';
68
+ }
69
+ if (typeof entry === 'string') {
70
+ return entry;
71
+ }
72
+ if (Array.isArray(entry)) {
73
+ return entry.map(extractToolResultSegment).filter(Boolean).join('');
74
+ }
75
+ if (typeof entry === 'object') {
76
+ const node = entry;
77
+ const type = typeof node.type === 'string' ? node.type.toLowerCase() : '';
78
+ if (type === 'input_text' || type === 'input_json' || type === 'tool_result_status' || type === 'status' || type === 'metadata') {
79
+ return '';
80
+ }
81
+ if (type === 'output_text' || type === 'text' || type === 'reasoning' || type === 'log') {
82
+ return flattenAnthropicText(entry);
83
+ }
84
+ if (type === 'output_json' || type === 'json') {
85
+ const payload = node.content ?? node.text ?? node.data ?? node.output;
86
+ if (payload === undefined) {
87
+ return '';
88
+ }
89
+ try {
90
+ return JSON.stringify(payload);
91
+ }
92
+ catch {
93
+ return String(payload ?? '');
94
+ }
95
+ }
96
+ if (typeof node.text === 'string') {
97
+ return node.text;
98
+ }
99
+ if ('content' in node) {
100
+ const nested = extractToolResultSegment(node.content);
101
+ if (nested) {
102
+ return nested;
103
+ }
104
+ }
105
+ try {
106
+ return JSON.stringify(entry);
107
+ }
108
+ catch {
109
+ return '';
110
+ }
111
+ }
112
+ return String(entry);
113
+ }
35
114
  function requireTrimmedString(value, context) {
36
115
  if (typeof value !== 'string') {
37
116
  throw new Error(`Anthropic bridge constraint violated: ${context} must be a string`);
@@ -49,9 +128,112 @@ function requireSystemText(block, context) {
49
128
  }
50
129
  return text;
51
130
  }
131
+ const ANTHROPIC_TOOL_NAME_ALIASES = new Map([
132
+ ['bash', 'shell_command'],
133
+ ['shell', 'shell_command'],
134
+ ['terminal', 'shell_command']
135
+ ]);
136
+ const CANONICAL_TO_ANTHROPIC_TOOL_NAMES = new Map([['shell_command', 'Bash']]);
137
+ const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
138
+ 'model',
139
+ 'messages',
140
+ 'tools',
141
+ 'system',
142
+ 'stop_sequences',
143
+ 'temperature',
144
+ 'top_p',
145
+ 'top_k',
146
+ 'max_tokens',
147
+ 'max_output_tokens',
148
+ 'metadata',
149
+ 'stream',
150
+ 'tool_choice'
151
+ ]);
152
+ export function normalizeAnthropicToolName(value) {
153
+ if (typeof value !== 'string') {
154
+ return undefined;
155
+ }
156
+ const trimmed = value.trim();
157
+ if (!trimmed) {
158
+ return undefined;
159
+ }
160
+ const lower = trimmed.toLowerCase();
161
+ const alias = ANTHROPIC_TOOL_NAME_ALIASES.get(lower);
162
+ if (alias) {
163
+ return alias;
164
+ }
165
+ if (lower.startsWith('mcp__')) {
166
+ return lower;
167
+ }
168
+ return lower;
169
+ }
170
+ export function denormalizeAnthropicToolName(value) {
171
+ if (typeof value !== 'string') {
172
+ return undefined;
173
+ }
174
+ const trimmed = value.trim();
175
+ if (!trimmed) {
176
+ return undefined;
177
+ }
178
+ const lower = trimmed.toLowerCase();
179
+ const mapped = CANONICAL_TO_ANTHROPIC_TOOL_NAMES.get(lower);
180
+ if (mapped) {
181
+ return mapped;
182
+ }
183
+ if (lower.startsWith('mcp__')) {
184
+ return trimmed;
185
+ }
186
+ return trimmed;
187
+ }
188
+ function invertAnthropicAliasMap(source) {
189
+ if (!source) {
190
+ return undefined;
191
+ }
192
+ const inverted = {};
193
+ for (const [canonical, raw] of Object.entries(source)) {
194
+ if (typeof canonical !== 'string' || typeof raw !== 'string') {
195
+ continue;
196
+ }
197
+ const trimmedCanonical = canonical.trim();
198
+ const trimmedRaw = raw.trim();
199
+ if (!trimmedCanonical.length) {
200
+ continue;
201
+ }
202
+ if (trimmedRaw.length) {
203
+ inverted[trimmedRaw.toLowerCase()] = trimmedCanonical;
204
+ }
205
+ if (!inverted[trimmedCanonical.toLowerCase()]) {
206
+ inverted[trimmedCanonical.toLowerCase()] = trimmedCanonical;
207
+ }
208
+ }
209
+ return Object.keys(inverted).length ? inverted : undefined;
210
+ }
52
211
  export function buildOpenAIChatFromAnthropic(payload) {
53
212
  const newMessages = [];
54
213
  const body = isObject(payload) ? payload : {};
214
+ const canonicalAliasMap = coerceAliasRecord(buildAnthropicToolAliasMap(body.tools));
215
+ const reverseAliasMap = invertAnthropicAliasMap(canonicalAliasMap);
216
+ const resolveToolName = (candidate) => {
217
+ if (typeof candidate !== 'string') {
218
+ return '';
219
+ }
220
+ const trimmed = candidate.trim();
221
+ if (!trimmed.length) {
222
+ return trimmed;
223
+ }
224
+ const normalized = normalizeAnthropicToolName(trimmed) ?? trimmed;
225
+ if (reverseAliasMap) {
226
+ const direct = reverseAliasMap[trimmed.toLowerCase()];
227
+ if (typeof direct === 'string' && direct.trim().length) {
228
+ return direct.trim();
229
+ }
230
+ const normalizedLookup = reverseAliasMap[normalized.toLowerCase()];
231
+ if (typeof normalizedLookup === 'string' && normalizedLookup.trim().length) {
232
+ return normalizedLookup.trim();
233
+ }
234
+ }
235
+ return normalized;
236
+ };
55
237
  const rawSystem = body.system;
56
238
  const systemBlocks = Array.isArray(rawSystem)
57
239
  ? rawSystem
@@ -107,40 +289,31 @@ export function buildOpenAIChatFromAnthropic(payload) {
107
289
  const id = requireTrimmedString(block.id, 'tool_use.id');
108
290
  const input = block.input ?? {};
109
291
  const args = safeJson(input);
110
- toolCalls.push({ id, type: 'function', function: { name, arguments: args } });
292
+ const canonicalName = resolveToolName(name) || name;
293
+ toolCalls.push({ id, type: 'function', function: { name: canonicalName, arguments: args } });
111
294
  }
112
295
  else if (t === 'tool_result') {
113
296
  const callId = requireTrimmedString(block.tool_call_id ??
114
297
  block.call_id ??
115
298
  block.tool_use_id ??
116
299
  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
- }
300
+ const contentStr = normalizeToolResultContent(block);
129
301
  toolResults.push({ role: 'tool', tool_call_id: callId, content: contentStr });
130
302
  }
131
303
  }
132
304
  const combinedText = textParts.join('\n');
133
305
  const normalized = normalizeChatMessageContent(combinedText);
306
+ const hasRawText = typeof combinedText === 'string' && combinedText.trim().length > 0;
134
307
  const mergedReasoning = [...reasoningParts];
135
308
  if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
136
309
  mergedReasoning.push(normalized.reasoningText.trim());
137
310
  }
138
311
  const hasText = typeof normalized.contentText === 'string' && normalized.contentText.length > 0;
139
312
  const hasReasoning = mergedReasoning.length > 0;
140
- if (hasText || toolCalls.length > 0 || hasReasoning) {
313
+ if (hasText || hasRawText || toolCalls.length > 0 || hasReasoning) {
141
314
  const msg = {
142
315
  role,
143
- content: normalized.contentText ?? combinedText ?? ''
316
+ content: (hasText ? normalized.contentText : undefined) ?? combinedText ?? ''
144
317
  };
145
318
  if (toolCalls.length)
146
319
  msg.tool_calls = toolCalls;
@@ -192,7 +365,7 @@ export function buildOpenAIChatFromAnthropic(payload) {
192
365
  }
193
366
  return request;
194
367
  }
195
- export function buildAnthropicFromOpenAIChat(oa) {
368
+ export function buildAnthropicFromOpenAIChat(oa, options) {
196
369
  const mapFinishReason = (reason) => {
197
370
  if (typeof reason !== 'string' || !reason.trim().length) {
198
371
  return undefined;
@@ -239,11 +412,13 @@ export function buildAnthropicFromOpenAIChat(oa) {
239
412
  blocks.push({ type: 'thinking', text: reasoningField.trim() });
240
413
  }
241
414
  const toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
415
+ const toolNameResolver = createAnthropicToolNameResolver(options?.toolNameMap ?? extractToolNameMapFromPayload(oa));
242
416
  for (const tc of toolCalls) {
243
417
  try {
244
418
  const id = requireTrimmedString(tc?.id, 'chat.tool_call.id');
245
419
  const fn = isObject(tc?.function) ? tc.function : {};
246
- const name = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
420
+ const canonicalName = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
421
+ const name = toolNameResolver ? toolNameResolver(canonicalName) : canonicalName;
247
422
  const argsRaw = fn.arguments;
248
423
  let input;
249
424
  if (typeof argsRaw === 'string') {
@@ -271,16 +446,143 @@ export function buildAnthropicFromOpenAIChat(oa) {
271
446
  typeof body.choices[0]?.finish_reason === 'string'
272
447
  ? String(body.choices[0].finish_reason).trim()
273
448
  : undefined;
274
- const stopReason = mapFinishReason(finishReason);
449
+ const stopReasonCandidate = mapFinishReason(finishReason) ||
450
+ mapFinishReason(typeof primary?.finish_reason === 'string' ? String(primary.finish_reason) : undefined) ||
451
+ (typeof body?.stop_reason === 'string' ? String(body.stop_reason).trim() : undefined) ||
452
+ (typeof primary?.stop_reason === 'string' ? String(primary.stop_reason).trim() : undefined);
453
+ const hasToolCalls = toolCalls.length > 0;
454
+ const stopReason = stopReasonCandidate ?? (hasToolCalls ? 'tool_use' : 'end_turn');
455
+ const resolveResponseId = () => {
456
+ const preferred = [
457
+ body?.id,
458
+ body?.response?.id,
459
+ body?.response_id
460
+ ];
461
+ for (const candidateRaw of preferred) {
462
+ if (typeof candidateRaw !== 'string')
463
+ continue;
464
+ const candidate = candidateRaw.trim();
465
+ if (!candidate.length)
466
+ continue;
467
+ if (candidate.startsWith('resp_')) {
468
+ return candidate;
469
+ }
470
+ }
471
+ const fromRequest = typeof options?.requestId === 'string' && options.requestId.trim().startsWith('resp_')
472
+ ? options.requestId.trim()
473
+ : undefined;
474
+ return fromRequest ?? `resp_${Date.now()}`;
475
+ };
476
+ const resolveCreated = () => {
477
+ const candidateNumbers = [
478
+ body?.created,
479
+ body?.created_at,
480
+ body?.response?.created,
481
+ body?.response?.created_at
482
+ ];
483
+ for (const candidate of candidateNumbers) {
484
+ if (typeof candidate === 'number' && Number.isFinite(candidate)) {
485
+ return Math.floor(candidate);
486
+ }
487
+ }
488
+ return Math.floor(Date.now() / 1000);
489
+ };
275
490
  return {
276
- id: `resp_${Date.now()}`,
491
+ id: resolveResponseId(),
277
492
  type: 'message',
278
493
  role,
279
494
  model: String(body.model || 'unknown'),
280
- created: Math.floor(Date.now() / 1000),
495
+ created: resolveCreated(),
281
496
  content: blocks,
282
497
  usage: inputTokens || outputTokens ? { input_tokens: inputTokens, output_tokens: outputTokens } : undefined,
283
- ...(stopReason ? { stop_reason: stopReason } : {})
498
+ stop_reason: stopReason
499
+ };
500
+ }
501
+ function extractToolNameMapFromPayload(payload) {
502
+ if (!payload || typeof payload !== 'object') {
503
+ return undefined;
504
+ }
505
+ const candidateSources = [
506
+ payload.anthropicToolNameMap,
507
+ payload.__anthropicToolNameMap,
508
+ payload.metadata &&
509
+ typeof payload.metadata === 'object'
510
+ ? payload.metadata.anthropicToolNameMap
511
+ : undefined,
512
+ payload.metadata &&
513
+ typeof payload.metadata === 'object'
514
+ ? payload.metadata.extraFields &&
515
+ typeof payload.metadata.extraFields === 'object'
516
+ ? payload.metadata.extraFields.anthropicToolNameMap
517
+ : undefined
518
+ : undefined,
519
+ payload.metadata &&
520
+ typeof payload.metadata === 'object' &&
521
+ payload.metadata.capturedContext &&
522
+ typeof payload.metadata.capturedContext === 'object'
523
+ ? (payload.metadata.capturedContext.__hub_capture &&
524
+ typeof payload.metadata.capturedContext.__hub_capture === 'object'
525
+ ? payload.metadata.capturedContext.__hub_capture.extraFields &&
526
+ typeof payload.metadata.capturedContext.__hub_capture.extraFields === 'object'
527
+ ? payload.metadata.capturedContext.__hub_capture.extraFields
528
+ : undefined
529
+ : undefined)
530
+ : undefined
531
+ ];
532
+ for (const candidate of candidateSources) {
533
+ const map = coerceAliasRecord(candidate);
534
+ if (map) {
535
+ return map;
536
+ }
537
+ }
538
+ return undefined;
539
+ }
540
+ function coerceAliasRecord(candidate) {
541
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
542
+ return undefined;
543
+ }
544
+ let hasEntry = false;
545
+ const output = {};
546
+ for (const [key, value] of Object.entries(candidate)) {
547
+ if (typeof key !== 'string' || typeof value !== 'string') {
548
+ continue;
549
+ }
550
+ const trimmedKey = key.trim();
551
+ if (!trimmedKey.length) {
552
+ continue;
553
+ }
554
+ output[trimmedKey] = value;
555
+ hasEntry = true;
556
+ }
557
+ return hasEntry ? output : undefined;
558
+ }
559
+ function createAnthropicToolNameResolver(source) {
560
+ if (!source) {
561
+ return undefined;
562
+ }
563
+ const lookup = new Map();
564
+ for (const [key, value] of Object.entries(source)) {
565
+ if (typeof key !== 'string' || typeof value !== 'string')
566
+ continue;
567
+ const canonical = key.trim();
568
+ if (!canonical.length)
569
+ continue;
570
+ const alias = value.trim() || canonical;
571
+ lookup.set(canonical, alias);
572
+ const lower = canonical.toLowerCase();
573
+ if (!lookup.has(lower)) {
574
+ lookup.set(lower, alias);
575
+ }
576
+ }
577
+ if (!lookup.size) {
578
+ return undefined;
579
+ }
580
+ return (name) => {
581
+ const trimmed = name.trim();
582
+ if (!trimmed.length) {
583
+ return name;
584
+ }
585
+ return lookup.get(trimmed) ?? lookup.get(trimmed.toLowerCase()) ?? trimmed;
284
586
  };
285
587
  }
286
588
  export function buildAnthropicRequestFromOpenAIChat(chatReq) {
@@ -323,6 +625,8 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
323
625
  return '';
324
626
  };
325
627
  const msgs = Array.isArray(requestBody?.messages) ? requestBody.messages : [];
628
+ const mirrorShapes = extractMirrorShapesFromRequest(requestBody);
629
+ let mirrorIndex = 0;
326
630
  const knownToolCallIds = new Set();
327
631
  for (const m of msgs) {
328
632
  if (!m || typeof m !== 'object')
@@ -375,6 +679,11 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
375
679
  if (!m || typeof m !== 'object')
376
680
  continue;
377
681
  const role = String(m.role || 'user');
682
+ let targetShape;
683
+ if (role !== 'system' && Array.isArray(mirrorShapes)) {
684
+ targetShape = mirrorShapes[mirrorIndex];
685
+ mirrorIndex += 1;
686
+ }
378
687
  const text = collectText(m.content).trim();
379
688
  if (role === 'system') {
380
689
  if (!text) {
@@ -426,7 +735,12 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
426
735
  blocks.push({ type: 'tool_use', id, name, input });
427
736
  }
428
737
  if (blocks.length > 0) {
429
- messages.push({ role, content: blocks });
738
+ const hasStructuredBlocks = blocks.some((block) => block && typeof block === 'object' && block.type !== 'text');
739
+ let contentNode = blocks;
740
+ if (targetShape === 'string' || (!targetShape && !hasStructuredBlocks)) {
741
+ contentNode = text;
742
+ }
743
+ messages.push({ role, content: contentNode });
430
744
  }
431
745
  }
432
746
  const out = { model };
@@ -467,7 +781,7 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
467
781
  else if (Array.isArray(stop) && stop.length > 0) {
468
782
  out.stop_sequences = stop.map((s) => String(s)).filter(Boolean);
469
783
  }
470
- return out;
784
+ return pruneAnthropicRequest(out);
471
785
  }
472
786
  function isPlainRecord(value) {
473
787
  return !!value && typeof value === 'object' && !Array.isArray(value);
@@ -560,7 +874,7 @@ function convertBridgeToolToAnthropic(def) {
560
874
  name,
561
875
  input_schema: inputSchema
562
876
  };
563
- if (description) {
877
+ if (description !== undefined) {
564
878
  tool.description = description;
565
879
  }
566
880
  return tool;
@@ -570,13 +884,13 @@ export function mapAnthropicToolsToChat(rawTools, missing) {
570
884
  if (prepared === undefined) {
571
885
  return undefined;
572
886
  }
573
- return mapBridgeToolsToChat(prepared);
887
+ return mapBridgeToolsToChat(prepared, { sanitizeName: normalizeAnthropicToolName });
574
888
  }
575
889
  export function mapChatToolsToAnthropicTools(rawTools) {
576
890
  if (!Array.isArray(rawTools) || rawTools.length === 0) {
577
891
  return undefined;
578
892
  }
579
- const bridgeDefs = mapChatToolsToBridge(rawTools);
893
+ const bridgeDefs = mapChatToolsToBridge(rawTools, { sanitizeName: denormalizeAnthropicToolName });
580
894
  if (!bridgeDefs || !bridgeDefs.length) {
581
895
  return undefined;
582
896
  }
@@ -585,3 +899,77 @@ export function mapChatToolsToAnthropicTools(rawTools) {
585
899
  .filter((entry) => !!entry);
586
900
  return converted.length ? converted : undefined;
587
901
  }
902
+ function pruneAnthropicRequest(payload) {
903
+ for (const key of Object.keys(payload)) {
904
+ if (!ANTHROPIC_TOP_LEVEL_FIELDS.has(key)) {
905
+ delete payload[key];
906
+ }
907
+ }
908
+ return payload;
909
+ }
910
+ function extractMirrorShapesFromRequest(source) {
911
+ const directMirror = source &&
912
+ typeof source === 'object' &&
913
+ source.__anthropicMirror &&
914
+ typeof source.__anthropicMirror === 'object'
915
+ ? source.__anthropicMirror
916
+ : extractMirrorFromMetadata(source);
917
+ if (!directMirror) {
918
+ return undefined;
919
+ }
920
+ const shapes = directMirror.messageContentShape;
921
+ if (!Array.isArray(shapes)) {
922
+ return undefined;
923
+ }
924
+ return shapes.map((entry) => (typeof entry === 'string' ? entry : String(entry ?? '')));
925
+ }
926
+ function extractMirrorFromMetadata(source) {
927
+ if (!source || typeof source !== 'object') {
928
+ return undefined;
929
+ }
930
+ const metadata = source.metadata;
931
+ if (!metadata || typeof metadata !== 'object') {
932
+ return undefined;
933
+ }
934
+ const extraFields = metadata.extraFields;
935
+ if (!extraFields || typeof extraFields !== 'object') {
936
+ return undefined;
937
+ }
938
+ const mirror = extraFields.anthropicMirror;
939
+ return mirror && typeof mirror === 'object' ? mirror : undefined;
940
+ }
941
+ export function buildAnthropicToolAliasMap(rawTools) {
942
+ if (!Array.isArray(rawTools) || rawTools.length === 0) {
943
+ return undefined;
944
+ }
945
+ const aliasMap = new Map();
946
+ for (const entry of rawTools) {
947
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
948
+ continue;
949
+ }
950
+ const rawName = typeof entry.name === 'string'
951
+ ? entry.name.trim()
952
+ : undefined;
953
+ if (!rawName) {
954
+ continue;
955
+ }
956
+ const normalized = normalizeAnthropicToolName(rawName) ?? rawName;
957
+ const canonicalKey = normalized.trim();
958
+ if (!canonicalKey.length) {
959
+ continue;
960
+ }
961
+ aliasMap.set(canonicalKey, rawName);
962
+ const lowerKey = canonicalKey.toLowerCase();
963
+ if (lowerKey !== canonicalKey && !aliasMap.has(lowerKey)) {
964
+ aliasMap.set(lowerKey, rawName);
965
+ }
966
+ }
967
+ if (!aliasMap.size) {
968
+ return undefined;
969
+ }
970
+ const serialized = {};
971
+ for (const [key, value] of aliasMap.entries()) {
972
+ serialized[key] = value;
973
+ }
974
+ return serialized;
975
+ }