@jsonstudio/llms 0.4.5 → 0.6.0

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 (92) 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 +11 -11
  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 +119 -59
  49. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +74 -13
  50. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +0 -9
  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 +44 -30
  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 -6
  61. package/dist/conversion/shared/anthropic-message-utils.d.ts +9 -1
  62. package/dist/conversion/shared/anthropic-message-utils.js +334 -14
  63. package/dist/conversion/shared/bridge-actions.js +267 -40
  64. package/dist/conversion/shared/bridge-message-utils.js +54 -8
  65. package/dist/conversion/shared/bridge-policies.js +29 -4
  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 +108 -25
  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/reasoning-normalizer.d.ts +1 -0
  74. package/dist/conversion/shared/reasoning-normalizer.js +50 -18
  75. package/dist/conversion/shared/responses-output-builder.d.ts +1 -1
  76. package/dist/conversion/shared/responses-output-builder.js +76 -25
  77. package/dist/conversion/shared/responses-reasoning-registry.d.ts +8 -0
  78. package/dist/conversion/shared/responses-reasoning-registry.js +61 -0
  79. package/dist/conversion/shared/responses-response-utils.js +32 -2
  80. package/dist/conversion/shared/responses-tool-utils.js +28 -2
  81. package/dist/conversion/shared/snapshot-hooks.d.ts +9 -0
  82. package/dist/conversion/shared/snapshot-hooks.js +60 -6
  83. package/dist/conversion/shared/snapshot-utils.d.ts +16 -0
  84. package/dist/conversion/shared/snapshot-utils.js +84 -0
  85. package/dist/conversion/shared/tool-filter-pipeline.js +45 -5
  86. package/dist/conversion/shared/tool-governor.js +5 -0
  87. package/dist/conversion/shared/tool-mapping.js +13 -2
  88. package/dist/filters/special/request-tool-choice-policy.js +3 -1
  89. package/dist/filters/special/request-tool-list-filter.d.ts +11 -0
  90. package/dist/filters/special/request-tool-list-filter.js +20 -7
  91. package/dist/sse/shared/responses-output-normalizer.js +5 -4
  92. package/package.json +1 -1
@@ -49,9 +49,112 @@ function requireSystemText(block, context) {
49
49
  }
50
50
  return text;
51
51
  }
52
+ const ANTHROPIC_TOOL_NAME_ALIASES = new Map([
53
+ ['bash', 'shell_command'],
54
+ ['shell', 'shell_command'],
55
+ ['terminal', 'shell_command']
56
+ ]);
57
+ const CANONICAL_TO_ANTHROPIC_TOOL_NAMES = new Map([['shell_command', 'Bash']]);
58
+ const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
59
+ 'model',
60
+ 'messages',
61
+ 'tools',
62
+ 'system',
63
+ 'stop_sequences',
64
+ 'temperature',
65
+ 'top_p',
66
+ 'top_k',
67
+ 'max_tokens',
68
+ 'max_output_tokens',
69
+ 'metadata',
70
+ 'stream',
71
+ 'tool_choice'
72
+ ]);
73
+ export function normalizeAnthropicToolName(value) {
74
+ if (typeof value !== 'string') {
75
+ return undefined;
76
+ }
77
+ const trimmed = value.trim();
78
+ if (!trimmed) {
79
+ return undefined;
80
+ }
81
+ const lower = trimmed.toLowerCase();
82
+ const alias = ANTHROPIC_TOOL_NAME_ALIASES.get(lower);
83
+ if (alias) {
84
+ return alias;
85
+ }
86
+ if (lower.startsWith('mcp__')) {
87
+ return lower;
88
+ }
89
+ return lower;
90
+ }
91
+ export function denormalizeAnthropicToolName(value) {
92
+ if (typeof value !== 'string') {
93
+ return undefined;
94
+ }
95
+ const trimmed = value.trim();
96
+ if (!trimmed) {
97
+ return undefined;
98
+ }
99
+ const lower = trimmed.toLowerCase();
100
+ const mapped = CANONICAL_TO_ANTHROPIC_TOOL_NAMES.get(lower);
101
+ if (mapped) {
102
+ return mapped;
103
+ }
104
+ if (lower.startsWith('mcp__')) {
105
+ return trimmed;
106
+ }
107
+ return trimmed;
108
+ }
109
+ function invertAnthropicAliasMap(source) {
110
+ if (!source) {
111
+ return undefined;
112
+ }
113
+ const inverted = {};
114
+ for (const [canonical, raw] of Object.entries(source)) {
115
+ if (typeof canonical !== 'string' || typeof raw !== 'string') {
116
+ continue;
117
+ }
118
+ const trimmedCanonical = canonical.trim();
119
+ const trimmedRaw = raw.trim();
120
+ if (!trimmedCanonical.length) {
121
+ continue;
122
+ }
123
+ if (trimmedRaw.length) {
124
+ inverted[trimmedRaw.toLowerCase()] = trimmedCanonical;
125
+ }
126
+ if (!inverted[trimmedCanonical.toLowerCase()]) {
127
+ inverted[trimmedCanonical.toLowerCase()] = trimmedCanonical;
128
+ }
129
+ }
130
+ return Object.keys(inverted).length ? inverted : undefined;
131
+ }
52
132
  export function buildOpenAIChatFromAnthropic(payload) {
53
133
  const newMessages = [];
54
134
  const body = isObject(payload) ? payload : {};
135
+ const canonicalAliasMap = coerceAliasRecord(buildAnthropicToolAliasMap(body.tools));
136
+ const reverseAliasMap = invertAnthropicAliasMap(canonicalAliasMap);
137
+ const resolveToolName = (candidate) => {
138
+ if (typeof candidate !== 'string') {
139
+ return '';
140
+ }
141
+ const trimmed = candidate.trim();
142
+ if (!trimmed.length) {
143
+ return trimmed;
144
+ }
145
+ const normalized = normalizeAnthropicToolName(trimmed) ?? trimmed;
146
+ if (reverseAliasMap) {
147
+ const direct = reverseAliasMap[trimmed.toLowerCase()];
148
+ if (typeof direct === 'string' && direct.trim().length) {
149
+ return direct.trim();
150
+ }
151
+ const normalizedLookup = reverseAliasMap[normalized.toLowerCase()];
152
+ if (typeof normalizedLookup === 'string' && normalizedLookup.trim().length) {
153
+ return normalizedLookup.trim();
154
+ }
155
+ }
156
+ return normalized;
157
+ };
55
158
  const rawSystem = body.system;
56
159
  const systemBlocks = Array.isArray(rawSystem)
57
160
  ? rawSystem
@@ -107,7 +210,8 @@ export function buildOpenAIChatFromAnthropic(payload) {
107
210
  const id = requireTrimmedString(block.id, 'tool_use.id');
108
211
  const input = block.input ?? {};
109
212
  const args = safeJson(input);
110
- toolCalls.push({ id, type: 'function', function: { name, arguments: args } });
213
+ const canonicalName = resolveToolName(name) || name;
214
+ toolCalls.push({ id, type: 'function', function: { name: canonicalName, arguments: args } });
111
215
  }
112
216
  else if (t === 'tool_result') {
113
217
  const callId = requireTrimmedString(block.tool_call_id ??
@@ -131,16 +235,17 @@ export function buildOpenAIChatFromAnthropic(payload) {
131
235
  }
132
236
  const combinedText = textParts.join('\n');
133
237
  const normalized = normalizeChatMessageContent(combinedText);
238
+ const hasRawText = typeof combinedText === 'string' && combinedText.trim().length > 0;
134
239
  const mergedReasoning = [...reasoningParts];
135
240
  if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
136
241
  mergedReasoning.push(normalized.reasoningText.trim());
137
242
  }
138
243
  const hasText = typeof normalized.contentText === 'string' && normalized.contentText.length > 0;
139
244
  const hasReasoning = mergedReasoning.length > 0;
140
- if (hasText || toolCalls.length > 0 || hasReasoning) {
245
+ if (hasText || hasRawText || toolCalls.length > 0 || hasReasoning) {
141
246
  const msg = {
142
247
  role,
143
- content: normalized.contentText ?? combinedText ?? ''
248
+ content: (hasText ? normalized.contentText : undefined) ?? combinedText ?? ''
144
249
  };
145
250
  if (toolCalls.length)
146
251
  msg.tool_calls = toolCalls;
@@ -192,7 +297,7 @@ export function buildOpenAIChatFromAnthropic(payload) {
192
297
  }
193
298
  return request;
194
299
  }
195
- export function buildAnthropicFromOpenAIChat(oa) {
300
+ export function buildAnthropicFromOpenAIChat(oa, options) {
196
301
  const mapFinishReason = (reason) => {
197
302
  if (typeof reason !== 'string' || !reason.trim().length) {
198
303
  return undefined;
@@ -239,11 +344,13 @@ export function buildAnthropicFromOpenAIChat(oa) {
239
344
  blocks.push({ type: 'thinking', text: reasoningField.trim() });
240
345
  }
241
346
  const toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
347
+ const toolNameResolver = createAnthropicToolNameResolver(options?.toolNameMap ?? extractToolNameMapFromPayload(oa));
242
348
  for (const tc of toolCalls) {
243
349
  try {
244
350
  const id = requireTrimmedString(tc?.id, 'chat.tool_call.id');
245
351
  const fn = isObject(tc?.function) ? tc.function : {};
246
- const name = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
352
+ const canonicalName = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
353
+ const name = toolNameResolver ? toolNameResolver(canonicalName) : canonicalName;
247
354
  const argsRaw = fn.arguments;
248
355
  let input;
249
356
  if (typeof argsRaw === 'string') {
@@ -271,16 +378,143 @@ export function buildAnthropicFromOpenAIChat(oa) {
271
378
  typeof body.choices[0]?.finish_reason === 'string'
272
379
  ? String(body.choices[0].finish_reason).trim()
273
380
  : undefined;
274
- const stopReason = mapFinishReason(finishReason);
381
+ const stopReasonCandidate = mapFinishReason(finishReason) ||
382
+ mapFinishReason(typeof primary?.finish_reason === 'string' ? String(primary.finish_reason) : undefined) ||
383
+ (typeof body?.stop_reason === 'string' ? String(body.stop_reason).trim() : undefined) ||
384
+ (typeof primary?.stop_reason === 'string' ? String(primary.stop_reason).trim() : undefined);
385
+ const hasToolCalls = toolCalls.length > 0;
386
+ const stopReason = stopReasonCandidate ?? (hasToolCalls ? 'tool_use' : 'end_turn');
387
+ const resolveResponseId = () => {
388
+ const preferred = [
389
+ body?.id,
390
+ body?.response?.id,
391
+ body?.response_id
392
+ ];
393
+ for (const candidateRaw of preferred) {
394
+ if (typeof candidateRaw !== 'string')
395
+ continue;
396
+ const candidate = candidateRaw.trim();
397
+ if (!candidate.length)
398
+ continue;
399
+ if (candidate.startsWith('resp_')) {
400
+ return candidate;
401
+ }
402
+ }
403
+ const fromRequest = typeof options?.requestId === 'string' && options.requestId.trim().startsWith('resp_')
404
+ ? options.requestId.trim()
405
+ : undefined;
406
+ return fromRequest ?? `resp_${Date.now()}`;
407
+ };
408
+ const resolveCreated = () => {
409
+ const candidateNumbers = [
410
+ body?.created,
411
+ body?.created_at,
412
+ body?.response?.created,
413
+ body?.response?.created_at
414
+ ];
415
+ for (const candidate of candidateNumbers) {
416
+ if (typeof candidate === 'number' && Number.isFinite(candidate)) {
417
+ return Math.floor(candidate);
418
+ }
419
+ }
420
+ return Math.floor(Date.now() / 1000);
421
+ };
275
422
  return {
276
- id: `resp_${Date.now()}`,
423
+ id: resolveResponseId(),
277
424
  type: 'message',
278
425
  role,
279
426
  model: String(body.model || 'unknown'),
280
- created: Math.floor(Date.now() / 1000),
427
+ created: resolveCreated(),
281
428
  content: blocks,
282
429
  usage: inputTokens || outputTokens ? { input_tokens: inputTokens, output_tokens: outputTokens } : undefined,
283
- ...(stopReason ? { stop_reason: stopReason } : {})
430
+ stop_reason: stopReason
431
+ };
432
+ }
433
+ function extractToolNameMapFromPayload(payload) {
434
+ if (!payload || typeof payload !== 'object') {
435
+ return undefined;
436
+ }
437
+ const candidateSources = [
438
+ payload.anthropicToolNameMap,
439
+ payload.__anthropicToolNameMap,
440
+ payload.metadata &&
441
+ typeof payload.metadata === 'object'
442
+ ? payload.metadata.anthropicToolNameMap
443
+ : undefined,
444
+ payload.metadata &&
445
+ typeof payload.metadata === 'object'
446
+ ? payload.metadata.extraFields &&
447
+ typeof payload.metadata.extraFields === 'object'
448
+ ? payload.metadata.extraFields.anthropicToolNameMap
449
+ : undefined
450
+ : undefined,
451
+ payload.metadata &&
452
+ typeof payload.metadata === 'object' &&
453
+ payload.metadata.capturedContext &&
454
+ typeof payload.metadata.capturedContext === 'object'
455
+ ? (payload.metadata.capturedContext.__hub_capture &&
456
+ typeof payload.metadata.capturedContext.__hub_capture === 'object'
457
+ ? payload.metadata.capturedContext.__hub_capture.extraFields &&
458
+ typeof payload.metadata.capturedContext.__hub_capture.extraFields === 'object'
459
+ ? payload.metadata.capturedContext.__hub_capture.extraFields
460
+ : undefined
461
+ : undefined)
462
+ : undefined
463
+ ];
464
+ for (const candidate of candidateSources) {
465
+ const map = coerceAliasRecord(candidate);
466
+ if (map) {
467
+ return map;
468
+ }
469
+ }
470
+ return undefined;
471
+ }
472
+ function coerceAliasRecord(candidate) {
473
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
474
+ return undefined;
475
+ }
476
+ let hasEntry = false;
477
+ const output = {};
478
+ for (const [key, value] of Object.entries(candidate)) {
479
+ if (typeof key !== 'string' || typeof value !== 'string') {
480
+ continue;
481
+ }
482
+ const trimmedKey = key.trim();
483
+ if (!trimmedKey.length) {
484
+ continue;
485
+ }
486
+ output[trimmedKey] = value;
487
+ hasEntry = true;
488
+ }
489
+ return hasEntry ? output : undefined;
490
+ }
491
+ function createAnthropicToolNameResolver(source) {
492
+ if (!source) {
493
+ return undefined;
494
+ }
495
+ const lookup = new Map();
496
+ for (const [key, value] of Object.entries(source)) {
497
+ if (typeof key !== 'string' || typeof value !== 'string')
498
+ continue;
499
+ const canonical = key.trim();
500
+ if (!canonical.length)
501
+ continue;
502
+ const alias = value.trim() || canonical;
503
+ lookup.set(canonical, alias);
504
+ const lower = canonical.toLowerCase();
505
+ if (!lookup.has(lower)) {
506
+ lookup.set(lower, alias);
507
+ }
508
+ }
509
+ if (!lookup.size) {
510
+ return undefined;
511
+ }
512
+ return (name) => {
513
+ const trimmed = name.trim();
514
+ if (!trimmed.length) {
515
+ return name;
516
+ }
517
+ return lookup.get(trimmed) ?? lookup.get(trimmed.toLowerCase()) ?? trimmed;
284
518
  };
285
519
  }
286
520
  export function buildAnthropicRequestFromOpenAIChat(chatReq) {
@@ -323,6 +557,8 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
323
557
  return '';
324
558
  };
325
559
  const msgs = Array.isArray(requestBody?.messages) ? requestBody.messages : [];
560
+ const mirrorShapes = extractMirrorShapesFromRequest(requestBody);
561
+ let mirrorIndex = 0;
326
562
  const knownToolCallIds = new Set();
327
563
  for (const m of msgs) {
328
564
  if (!m || typeof m !== 'object')
@@ -375,6 +611,11 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
375
611
  if (!m || typeof m !== 'object')
376
612
  continue;
377
613
  const role = String(m.role || 'user');
614
+ let targetShape;
615
+ if (role !== 'system' && Array.isArray(mirrorShapes)) {
616
+ targetShape = mirrorShapes[mirrorIndex];
617
+ mirrorIndex += 1;
618
+ }
378
619
  const text = collectText(m.content).trim();
379
620
  if (role === 'system') {
380
621
  if (!text) {
@@ -426,7 +667,12 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
426
667
  blocks.push({ type: 'tool_use', id, name, input });
427
668
  }
428
669
  if (blocks.length > 0) {
429
- messages.push({ role, content: blocks });
670
+ const hasStructuredBlocks = blocks.some((block) => block && typeof block === 'object' && block.type !== 'text');
671
+ let contentNode = blocks;
672
+ if (targetShape === 'string' || (!targetShape && !hasStructuredBlocks)) {
673
+ contentNode = text;
674
+ }
675
+ messages.push({ role, content: contentNode });
430
676
  }
431
677
  }
432
678
  const out = { model };
@@ -467,7 +713,7 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
467
713
  else if (Array.isArray(stop) && stop.length > 0) {
468
714
  out.stop_sequences = stop.map((s) => String(s)).filter(Boolean);
469
715
  }
470
- return out;
716
+ return pruneAnthropicRequest(out);
471
717
  }
472
718
  function isPlainRecord(value) {
473
719
  return !!value && typeof value === 'object' && !Array.isArray(value);
@@ -560,7 +806,7 @@ function convertBridgeToolToAnthropic(def) {
560
806
  name,
561
807
  input_schema: inputSchema
562
808
  };
563
- if (description) {
809
+ if (description !== undefined) {
564
810
  tool.description = description;
565
811
  }
566
812
  return tool;
@@ -570,13 +816,13 @@ export function mapAnthropicToolsToChat(rawTools, missing) {
570
816
  if (prepared === undefined) {
571
817
  return undefined;
572
818
  }
573
- return mapBridgeToolsToChat(prepared);
819
+ return mapBridgeToolsToChat(prepared, { sanitizeName: normalizeAnthropicToolName });
574
820
  }
575
821
  export function mapChatToolsToAnthropicTools(rawTools) {
576
822
  if (!Array.isArray(rawTools) || rawTools.length === 0) {
577
823
  return undefined;
578
824
  }
579
- const bridgeDefs = mapChatToolsToBridge(rawTools);
825
+ const bridgeDefs = mapChatToolsToBridge(rawTools, { sanitizeName: denormalizeAnthropicToolName });
580
826
  if (!bridgeDefs || !bridgeDefs.length) {
581
827
  return undefined;
582
828
  }
@@ -585,3 +831,77 @@ export function mapChatToolsToAnthropicTools(rawTools) {
585
831
  .filter((entry) => !!entry);
586
832
  return converted.length ? converted : undefined;
587
833
  }
834
+ function pruneAnthropicRequest(payload) {
835
+ for (const key of Object.keys(payload)) {
836
+ if (!ANTHROPIC_TOP_LEVEL_FIELDS.has(key)) {
837
+ delete payload[key];
838
+ }
839
+ }
840
+ return payload;
841
+ }
842
+ function extractMirrorShapesFromRequest(source) {
843
+ const directMirror = source &&
844
+ typeof source === 'object' &&
845
+ source.__anthropicMirror &&
846
+ typeof source.__anthropicMirror === 'object'
847
+ ? source.__anthropicMirror
848
+ : extractMirrorFromMetadata(source);
849
+ if (!directMirror) {
850
+ return undefined;
851
+ }
852
+ const shapes = directMirror.messageContentShape;
853
+ if (!Array.isArray(shapes)) {
854
+ return undefined;
855
+ }
856
+ return shapes.map((entry) => (typeof entry === 'string' ? entry : String(entry ?? '')));
857
+ }
858
+ function extractMirrorFromMetadata(source) {
859
+ if (!source || typeof source !== 'object') {
860
+ return undefined;
861
+ }
862
+ const metadata = source.metadata;
863
+ if (!metadata || typeof metadata !== 'object') {
864
+ return undefined;
865
+ }
866
+ const extraFields = metadata.extraFields;
867
+ if (!extraFields || typeof extraFields !== 'object') {
868
+ return undefined;
869
+ }
870
+ const mirror = extraFields.anthropicMirror;
871
+ return mirror && typeof mirror === 'object' ? mirror : undefined;
872
+ }
873
+ export function buildAnthropicToolAliasMap(rawTools) {
874
+ if (!Array.isArray(rawTools) || rawTools.length === 0) {
875
+ return undefined;
876
+ }
877
+ const aliasMap = new Map();
878
+ for (const entry of rawTools) {
879
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
880
+ continue;
881
+ }
882
+ const rawName = typeof entry.name === 'string'
883
+ ? entry.name.trim()
884
+ : undefined;
885
+ if (!rawName) {
886
+ continue;
887
+ }
888
+ const normalized = normalizeAnthropicToolName(rawName) ?? rawName;
889
+ const canonicalKey = normalized.trim();
890
+ if (!canonicalKey.length) {
891
+ continue;
892
+ }
893
+ aliasMap.set(canonicalKey, rawName);
894
+ const lowerKey = canonicalKey.toLowerCase();
895
+ if (lowerKey !== canonicalKey && !aliasMap.has(lowerKey)) {
896
+ aliasMap.set(lowerKey, rawName);
897
+ }
898
+ }
899
+ if (!aliasMap.size) {
900
+ return undefined;
901
+ }
902
+ const serialized = {};
903
+ for (const [key, value] of aliasMap.entries()) {
904
+ serialized[key] = value;
905
+ }
906
+ return serialized;
907
+ }