@jsonstudio/llms 0.4.6 → 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.
- package/dist/conversion/codecs/anthropic-openai-codec.js +28 -2
- package/dist/conversion/codecs/gemini-openai-codec.js +23 -0
- package/dist/conversion/codecs/responses-openai-codec.js +8 -1
- package/dist/conversion/hub/node-support.js +14 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +66 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +284 -193
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +11 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +16 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +17 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.d.ts +5 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +17 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.d.ts +19 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +269 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +18 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +141 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +11 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +29 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +16 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +15 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +17 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +18 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.d.ts +17 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +63 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.d.ts +11 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +12 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +13 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +43 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.d.ts +17 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +22 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.d.ts +16 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +19 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.d.ts +17 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +19 -0
- package/dist/conversion/hub/pipeline/stages/utils.d.ts +2 -0
- package/dist/conversion/hub/pipeline/stages/utils.js +11 -0
- package/dist/conversion/hub/pipeline/target-utils.d.ts +5 -0
- package/dist/conversion/hub/pipeline/target-utils.js +87 -0
- package/dist/conversion/hub/process/chat-process.js +11 -11
- package/dist/conversion/hub/response/provider-response.js +69 -122
- package/dist/conversion/hub/response/response-mappers.d.ts +19 -0
- package/dist/conversion/hub/response/response-mappers.js +22 -2
- package/dist/conversion/hub/response/response-runtime.d.ts +8 -0
- package/dist/conversion/hub/response/response-runtime.js +239 -6
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +8 -0
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +119 -59
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +74 -13
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +0 -9
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +16 -13
- package/dist/conversion/hub/snapshot-recorder.d.ts +13 -0
- package/dist/conversion/hub/snapshot-recorder.js +90 -50
- package/dist/conversion/hub/standardized-bridge.js +44 -30
- package/dist/conversion/hub/types/chat-envelope.d.ts +68 -0
- package/dist/conversion/hub/types/standardized.d.ts +97 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +29 -2
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +68 -1
- package/dist/conversion/responses/responses-openai-bridge.d.ts +6 -1
- package/dist/conversion/responses/responses-openai-bridge.js +132 -6
- package/dist/conversion/shared/anthropic-message-utils.d.ts +9 -1
- package/dist/conversion/shared/anthropic-message-utils.js +334 -14
- package/dist/conversion/shared/bridge-actions.js +267 -40
- package/dist/conversion/shared/bridge-message-utils.js +54 -8
- package/dist/conversion/shared/bridge-policies.js +29 -4
- package/dist/conversion/shared/chat-envelope-validator.d.ts +8 -0
- package/dist/conversion/shared/chat-envelope-validator.js +128 -0
- package/dist/conversion/shared/chat-request-filters.js +108 -25
- package/dist/conversion/shared/mcp-injection.js +41 -20
- package/dist/conversion/shared/openai-finalizer.d.ts +11 -0
- package/dist/conversion/shared/openai-finalizer.js +73 -0
- package/dist/conversion/shared/openai-message-normalize.js +32 -31
- package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
- package/dist/conversion/shared/reasoning-normalizer.js +50 -18
- package/dist/conversion/shared/responses-output-builder.d.ts +1 -1
- package/dist/conversion/shared/responses-output-builder.js +76 -25
- package/dist/conversion/shared/responses-reasoning-registry.d.ts +8 -0
- package/dist/conversion/shared/responses-reasoning-registry.js +61 -0
- package/dist/conversion/shared/responses-response-utils.js +32 -2
- package/dist/conversion/shared/responses-tool-utils.js +28 -2
- package/dist/conversion/shared/snapshot-hooks.d.ts +9 -0
- package/dist/conversion/shared/snapshot-hooks.js +60 -6
- package/dist/conversion/shared/snapshot-utils.d.ts +16 -0
- package/dist/conversion/shared/snapshot-utils.js +84 -0
- package/dist/conversion/shared/tool-filter-pipeline.js +45 -5
- package/dist/conversion/shared/tool-mapping.js +13 -2
- package/dist/filters/special/request-tool-choice-policy.js +3 -1
- package/dist/filters/special/request-tool-list-filter.d.ts +11 -0
- package/dist/filters/special/request-tool-list-filter.js +20 -7
- package/dist/sse/shared/responses-output-normalizer.js +5 -4
- 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
|
-
|
|
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
|
|
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
|
|
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:
|
|
423
|
+
id: resolveResponseId(),
|
|
277
424
|
type: 'message',
|
|
278
425
|
role,
|
|
279
426
|
model: String(body.model || 'unknown'),
|
|
280
|
-
created:
|
|
427
|
+
created: resolveCreated(),
|
|
281
428
|
content: blocks,
|
|
282
429
|
usage: inputTokens || outputTokens ? { input_tokens: inputTokens, output_tokens: outputTokens } : undefined,
|
|
283
|
-
|
|
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
|
-
|
|
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
|
+
}
|