@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.
- 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 +23 -17
- 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 +135 -55
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +80 -40
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +5 -29
- 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 +49 -38
- 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 -10
- package/dist/conversion/shared/anthropic-message-utils.d.ts +9 -1
- package/dist/conversion/shared/anthropic-message-utils.js +414 -26
- package/dist/conversion/shared/bridge-actions.js +267 -95
- package/dist/conversion/shared/bridge-message-utils.js +54 -8
- package/dist/conversion/shared/bridge-policies.js +21 -2
- 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 +109 -28
- 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/protocol-state.d.ts +4 -0
- package/dist/conversion/shared/protocol-state.js +23 -0
- 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 +46 -7
- package/dist/conversion/shared/tool-mapping.js +13 -2
- package/dist/filters/index.d.ts +18 -0
- package/dist/filters/index.js +0 -1
- package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +13 -0
- package/dist/filters/special/request-streaming-to-nonstreaming.js +13 -1
- 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/dist/sse/sse-to-json/builders/response-builder.js +24 -1
- package/dist/sse/types/responses-types.d.ts +2 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
491
|
+
id: resolveResponseId(),
|
|
277
492
|
type: 'message',
|
|
278
493
|
role,
|
|
279
494
|
model: String(body.model || 'unknown'),
|
|
280
|
-
created:
|
|
495
|
+
created: resolveCreated(),
|
|
281
496
|
content: blocks,
|
|
282
497
|
usage: inputTokens || outputTokens ? { input_tokens: inputTokens, output_tokens: outputTokens } : undefined,
|
|
283
|
-
|
|
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
|
-
|
|
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
|
+
}
|