@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
|
@@ -2,6 +2,8 @@ import { extractToolCallsFromReasoningText } from '../../shared/reasoning-tool-p
|
|
|
2
2
|
import { deriveToolCallKey } from '../../shared/tool-call-utils.js';
|
|
3
3
|
import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
|
|
4
4
|
import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
|
|
5
|
+
import { normalizeAnthropicToolName } from '../../shared/anthropic-message-utils.js';
|
|
6
|
+
import { registerResponsesReasoning, consumeResponsesReasoning, registerResponsesOutputTextMeta, consumeResponsesOutputTextMeta } from '../../shared/responses-reasoning-registry.js';
|
|
5
7
|
function flattenAnthropicContent(content) {
|
|
6
8
|
if (typeof content === 'string')
|
|
7
9
|
return content;
|
|
@@ -19,12 +21,106 @@ function flattenAnthropicContent(content) {
|
|
|
19
21
|
}
|
|
20
22
|
return '';
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
function createToolNameResolver(options) {
|
|
25
|
+
const reverse = new Map();
|
|
26
|
+
const aliasMap = options?.aliasMap;
|
|
27
|
+
if (aliasMap && typeof aliasMap === 'object') {
|
|
28
|
+
for (const [canonical, providerName] of Object.entries(aliasMap)) {
|
|
29
|
+
if (typeof canonical !== 'string' || typeof providerName !== 'string')
|
|
30
|
+
continue;
|
|
31
|
+
const normalizedProvider = providerName.trim().toLowerCase();
|
|
32
|
+
if (!normalizedProvider.length)
|
|
33
|
+
continue;
|
|
34
|
+
if (!reverse.has(normalizedProvider)) {
|
|
35
|
+
reverse.set(normalizedProvider, canonical.trim());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return (rawName) => {
|
|
40
|
+
const trimmed = typeof rawName === 'string' ? rawName.trim() : '';
|
|
41
|
+
if (!trimmed.length) {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
const lookup = reverse.get(trimmed.toLowerCase());
|
|
45
|
+
if (lookup && lookup.trim().length) {
|
|
46
|
+
return lookup.trim();
|
|
47
|
+
}
|
|
48
|
+
const normalized = normalizeAnthropicToolName(trimmed);
|
|
49
|
+
return (normalized && normalized.trim().length ? normalized : trimmed).trim();
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function extractAliasMapFromChatPayload(payload) {
|
|
53
|
+
const metadata = payload?.metadata;
|
|
54
|
+
if (metadata && typeof metadata === 'object' && metadata.anthropicToolNameMap) {
|
|
55
|
+
const candidate = metadata.anthropicToolNameMap;
|
|
56
|
+
if (candidate && typeof candidate === 'object') {
|
|
57
|
+
const serialized = {};
|
|
58
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
59
|
+
if (typeof key === 'string' && typeof value === 'string') {
|
|
60
|
+
const trimmedKey = key.trim();
|
|
61
|
+
const trimmedValue = value.trim();
|
|
62
|
+
if (trimmedKey.length && trimmedValue.length) {
|
|
63
|
+
serialized[trimmedKey] = trimmedValue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (Object.keys(serialized).length) {
|
|
68
|
+
return serialized;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (payload?.anthropicToolNameMap && typeof payload.anthropicToolNameMap === 'object') {
|
|
73
|
+
const map = {};
|
|
74
|
+
for (const [key, value] of Object.entries(payload.anthropicToolNameMap)) {
|
|
75
|
+
if (typeof key === 'string' && typeof value === 'string') {
|
|
76
|
+
const trimmedKey = key.trim();
|
|
77
|
+
const trimmedValue = value.trim();
|
|
78
|
+
if (trimmedKey.length && trimmedValue.length) {
|
|
79
|
+
map[trimmedKey] = trimmedValue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (Object.keys(map).length) {
|
|
84
|
+
return map;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
function createToolAliasSerializer(aliasMap) {
|
|
90
|
+
if (!aliasMap || typeof aliasMap !== 'object') {
|
|
91
|
+
return (name) => name;
|
|
92
|
+
}
|
|
93
|
+
const lookup = new Map();
|
|
94
|
+
for (const [canonical, providerName] of Object.entries(aliasMap)) {
|
|
95
|
+
if (typeof canonical !== 'string' || typeof providerName !== 'string')
|
|
96
|
+
continue;
|
|
97
|
+
const key = canonical.trim().toLowerCase();
|
|
98
|
+
if (!key.length)
|
|
99
|
+
continue;
|
|
100
|
+
if (!lookup.has(key)) {
|
|
101
|
+
lookup.set(key, providerName);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!lookup.size) {
|
|
105
|
+
return (name) => name;
|
|
106
|
+
}
|
|
107
|
+
return (name) => {
|
|
108
|
+
const trimmed = typeof name === 'string' ? name.trim() : '';
|
|
109
|
+
if (!trimmed.length) {
|
|
110
|
+
return name;
|
|
111
|
+
}
|
|
112
|
+
const resolved = lookup.get(trimmed.toLowerCase());
|
|
113
|
+
return resolved ? resolved : name;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function buildOpenAIChatFromAnthropicMessage(payload, options) {
|
|
23
117
|
const content = Array.isArray(payload?.content) ? payload.content : [];
|
|
24
118
|
const textParts = [];
|
|
25
119
|
const toolCalls = [];
|
|
120
|
+
const aliasCollector = {};
|
|
26
121
|
const inferredToolCalls = [];
|
|
27
122
|
const reasoningParts = [];
|
|
123
|
+
const resolveToolName = createToolNameResolver(options);
|
|
28
124
|
if (typeof payload?.reasoning_content === 'string' && payload.reasoning_content.trim().length) {
|
|
29
125
|
reasoningParts.push(String(payload.reasoning_content).trim());
|
|
30
126
|
}
|
|
@@ -36,9 +132,10 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
36
132
|
textParts.push(part.text);
|
|
37
133
|
}
|
|
38
134
|
else if (kind === 'tool_use') {
|
|
39
|
-
const
|
|
135
|
+
const rawName = typeof part.name === 'string'
|
|
40
136
|
? String(part.name)
|
|
41
137
|
: '';
|
|
138
|
+
const name = rawName ? resolveToolName(rawName) : '';
|
|
42
139
|
const id = typeof part.id === 'string'
|
|
43
140
|
? String(part.id)
|
|
44
141
|
: `call_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -57,6 +154,10 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
57
154
|
}
|
|
58
155
|
if (name) {
|
|
59
156
|
toolCalls.push({ id, name, args });
|
|
157
|
+
const trimmedRaw = rawName.trim();
|
|
158
|
+
if (trimmedRaw.length && !aliasCollector[name]) {
|
|
159
|
+
aliasCollector[name] = trimmedRaw;
|
|
160
|
+
}
|
|
60
161
|
}
|
|
61
162
|
}
|
|
62
163
|
else if (kind === 'thinking' || kind === 'reasoning') {
|
|
@@ -137,7 +238,7 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
137
238
|
}
|
|
138
239
|
const stopReason = typeof payload['stop_reason'] === 'string' ? payload['stop_reason'] : undefined;
|
|
139
240
|
const finishReason = canonicalToolCalls.length ? 'tool_calls' : mapFinishReason(stopReason);
|
|
140
|
-
|
|
241
|
+
const chatResponse = {
|
|
141
242
|
id: typeof payload.id === 'string' ? payload.id : `chatcmpl_${Date.now()}`,
|
|
142
243
|
object: 'chat.completion',
|
|
143
244
|
created: typeof payload?.['created'] === 'number' ? payload['created'] : Math.floor(Date.now() / 1000),
|
|
@@ -153,10 +254,24 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
153
254
|
? payload['usage']
|
|
154
255
|
: undefined
|
|
155
256
|
};
|
|
257
|
+
const preserved = consumeResponsesReasoning(chatResponse.id);
|
|
258
|
+
if (preserved && preserved.length) {
|
|
259
|
+
chatResponse.__responses_reasoning = preserved;
|
|
260
|
+
}
|
|
261
|
+
const preservedOutputMeta = consumeResponsesOutputTextMeta(chatResponse.id);
|
|
262
|
+
if (preservedOutputMeta) {
|
|
263
|
+
chatResponse.__responses_output_text_meta = preservedOutputMeta;
|
|
264
|
+
}
|
|
265
|
+
if (Object.keys(aliasCollector).length && !chatResponse.anthropicToolNameMap) {
|
|
266
|
+
chatResponse.anthropicToolNameMap = aliasCollector;
|
|
267
|
+
}
|
|
268
|
+
return chatResponse;
|
|
156
269
|
}
|
|
157
|
-
export function buildAnthropicResponseFromChat(chatResponse) {
|
|
270
|
+
export function buildAnthropicResponseFromChat(chatResponse, options) {
|
|
158
271
|
const choice = Array.isArray(chatResponse?.choices) ? chatResponse.choices[0] : undefined;
|
|
159
272
|
const message = choice && typeof choice === 'object' ? choice.message : undefined;
|
|
273
|
+
const aliasMap = options?.aliasMap ?? extractAliasMapFromChatPayload(chatResponse);
|
|
274
|
+
const outboundAliasSerializer = createToolAliasSerializer(aliasMap);
|
|
160
275
|
if (message) {
|
|
161
276
|
try {
|
|
162
277
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
@@ -198,6 +313,7 @@ export function buildAnthropicResponseFromChat(chatResponse) {
|
|
|
198
313
|
const fn = call.function || {};
|
|
199
314
|
if (typeof fn?.name !== 'string')
|
|
200
315
|
continue;
|
|
316
|
+
const serializedName = outboundAliasSerializer(fn.name);
|
|
201
317
|
let parsedArgs = {};
|
|
202
318
|
const args = fn.arguments;
|
|
203
319
|
if (typeof args === 'string') {
|
|
@@ -214,10 +330,22 @@ export function buildAnthropicResponseFromChat(chatResponse) {
|
|
|
214
330
|
contentBlocks.push({
|
|
215
331
|
type: 'tool_use',
|
|
216
332
|
id: typeof call.id === 'string' ? call.id : `call_${Math.random().toString(36).slice(2, 8)}`,
|
|
217
|
-
name:
|
|
333
|
+
name: serializedName,
|
|
218
334
|
input: parsedArgs
|
|
219
335
|
});
|
|
220
336
|
}
|
|
337
|
+
const toolResults = extractToolResultBlocks(chatResponse);
|
|
338
|
+
for (const block of toolResults) {
|
|
339
|
+
const sanitized = {
|
|
340
|
+
type: 'tool_result',
|
|
341
|
+
tool_use_id: block.tool_use_id,
|
|
342
|
+
content: block.content ?? ''
|
|
343
|
+
};
|
|
344
|
+
if (typeof block.is_error === 'boolean') {
|
|
345
|
+
sanitized.is_error = block.is_error;
|
|
346
|
+
}
|
|
347
|
+
contentBlocks.push(sanitized);
|
|
348
|
+
}
|
|
221
349
|
const usage = chatResponse?.usage;
|
|
222
350
|
const stopReason = typeof choice?.finish_reason === 'string'
|
|
223
351
|
? choice.finish_reason
|
|
@@ -244,7 +372,14 @@ export function buildAnthropicResponseFromChat(chatResponse) {
|
|
|
244
372
|
}
|
|
245
373
|
: undefined
|
|
246
374
|
};
|
|
247
|
-
|
|
375
|
+
const sanitized = sanitizeAnthropicMessage(raw);
|
|
376
|
+
if (Array.isArray(chatResponse?.__responses_reasoning)) {
|
|
377
|
+
registerResponsesReasoning(sanitized.id, chatResponse.__responses_reasoning);
|
|
378
|
+
}
|
|
379
|
+
if (chatResponse?.__responses_output_text_meta) {
|
|
380
|
+
registerResponsesOutputTextMeta(sanitized.id, chatResponse.__responses_output_text_meta);
|
|
381
|
+
}
|
|
382
|
+
return sanitized;
|
|
248
383
|
}
|
|
249
384
|
function sanitizeAnthropicMessage(message) {
|
|
250
385
|
const sanitized = {};
|
|
@@ -276,6 +411,17 @@ function sanitizeContentBlock(block) {
|
|
|
276
411
|
return null;
|
|
277
412
|
return { type: 'text', text: block.text };
|
|
278
413
|
}
|
|
414
|
+
if (type === 'thinking' || type === 'reasoning') {
|
|
415
|
+
const text = typeof block.text === 'string'
|
|
416
|
+
? block.text
|
|
417
|
+
: flattenAnthropicContent(block.content);
|
|
418
|
+
if (!text || !String(text).trim().length)
|
|
419
|
+
return null;
|
|
420
|
+
return {
|
|
421
|
+
type: type === 'reasoning' ? 'reasoning' : 'thinking',
|
|
422
|
+
text: String(text)
|
|
423
|
+
};
|
|
424
|
+
}
|
|
279
425
|
if (type === 'tool_use') {
|
|
280
426
|
const id = typeof block.id === 'string' && block.id.trim() ? block.id : `call_${Math.random().toString(36).slice(2, 8)}`;
|
|
281
427
|
const name = typeof block.name === 'string' ? block.name : '';
|
|
@@ -303,3 +449,90 @@ function sanitizeContentBlock(block) {
|
|
|
303
449
|
}
|
|
304
450
|
return null;
|
|
305
451
|
}
|
|
452
|
+
function extractToolResultBlocks(chatResponse) {
|
|
453
|
+
const results = [];
|
|
454
|
+
const seen = new Set();
|
|
455
|
+
const append = (candidate) => {
|
|
456
|
+
if (!candidate)
|
|
457
|
+
return;
|
|
458
|
+
if (seen.has(candidate.tool_use_id)) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
seen.add(candidate.tool_use_id);
|
|
462
|
+
results.push(candidate);
|
|
463
|
+
};
|
|
464
|
+
const primary = Array.isArray(chatResponse.tool_outputs)
|
|
465
|
+
? chatResponse.tool_outputs
|
|
466
|
+
: [];
|
|
467
|
+
primary.forEach(entry => append(normalizeToolResultEntry(entry)));
|
|
468
|
+
const meta = chatResponse.metadata;
|
|
469
|
+
if (meta && typeof meta === 'object') {
|
|
470
|
+
const captured = meta.capturedToolResults;
|
|
471
|
+
if (Array.isArray(captured)) {
|
|
472
|
+
captured.forEach(entry => append(normalizeToolResultEntry(entry)));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (choiceHasCapturedResults(chatResponse)) {
|
|
476
|
+
try {
|
|
477
|
+
const choice = Array.isArray(chatResponse?.choices) ? chatResponse.choices[0] : undefined;
|
|
478
|
+
const msgMeta = choice && typeof choice === 'object' ? choice.message : undefined;
|
|
479
|
+
const captured = msgMeta && typeof msgMeta === 'object'
|
|
480
|
+
? msgMeta.capturedToolResults
|
|
481
|
+
: undefined;
|
|
482
|
+
if (Array.isArray(captured)) {
|
|
483
|
+
captured.forEach(entry => append(normalizeToolResultEntry(entry)));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
/* ignore best-effort metadata extraction */
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return results;
|
|
491
|
+
}
|
|
492
|
+
function choiceHasCapturedResults(chatResponse) {
|
|
493
|
+
if (!Array.isArray(chatResponse?.choices)) {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
const first = chatResponse.choices[0];
|
|
497
|
+
if (!first || typeof first !== 'object') {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
const message = first.message;
|
|
501
|
+
if (!message || typeof message !== 'object') {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
return Array.isArray(message.capturedToolResults);
|
|
505
|
+
}
|
|
506
|
+
function normalizeToolResultEntry(entry) {
|
|
507
|
+
if (!entry || typeof entry !== 'object') {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
const rawId = entry.tool_call_id ?? entry.call_id ?? entry.id;
|
|
511
|
+
if (typeof rawId !== 'string' || !rawId.trim().length) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
const toolUseId = rawId.trim();
|
|
515
|
+
const rawContent = 'content' in entry ? entry.content : entry.output;
|
|
516
|
+
const content = normalizeToolContent(rawContent);
|
|
517
|
+
const isError = typeof entry.is_error === 'boolean' ? entry.is_error : undefined;
|
|
518
|
+
return {
|
|
519
|
+
tool_use_id: toolUseId,
|
|
520
|
+
content,
|
|
521
|
+
is_error: isError
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function normalizeToolContent(value) {
|
|
525
|
+
if (value == null) {
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
if (typeof value === 'string') {
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const serialized = JSON.stringify(value);
|
|
533
|
+
return serialized;
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
return String(value);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SemanticMapper } from '../format-adapters/index.js';
|
|
2
|
+
import type { AdapterContext, ChatEnvelope } from '../types/chat-envelope.js';
|
|
3
|
+
import type { FormatEnvelope } from '../types/format-envelope.js';
|
|
4
|
+
export declare class AnthropicSemanticMapper implements SemanticMapper {
|
|
5
|
+
private readonly chatMapper;
|
|
6
|
+
toChat(format: FormatEnvelope, ctx: AdapterContext): Promise<ChatEnvelope>;
|
|
7
|
+
fromChat(chat: ChatEnvelope, ctx: AdapterContext): Promise<FormatEnvelope>;
|
|
8
|
+
}
|
|
@@ -3,7 +3,8 @@ import { buildOpenAIChatFromAnthropic, buildAnthropicRequestFromOpenAIChat } fro
|
|
|
3
3
|
import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
|
|
4
4
|
import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
|
|
5
5
|
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../shared/metadata-passthrough.js';
|
|
6
|
-
import {
|
|
6
|
+
import { buildAnthropicToolAliasMap } from '../../shared/anthropic-message-utils.js';
|
|
7
|
+
import { ChatSemanticMapper } from './chat-mapper.js';
|
|
7
8
|
const ANTHROPIC_PARAMETER_KEYS = [
|
|
8
9
|
'model',
|
|
9
10
|
'temperature',
|
|
@@ -32,6 +33,14 @@ const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
|
|
|
32
33
|
]);
|
|
33
34
|
const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
|
|
34
35
|
const PASSTHROUGH_PARAMETERS = ['tool_choice'];
|
|
36
|
+
function sanitizeAnthropicPayload(payload) {
|
|
37
|
+
for (const key of Object.keys(payload)) {
|
|
38
|
+
if (!ANTHROPIC_TOP_LEVEL_FIELDS.has(key)) {
|
|
39
|
+
delete payload[key];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return payload;
|
|
43
|
+
}
|
|
35
44
|
function collectParameters(payload) {
|
|
36
45
|
const params = {};
|
|
37
46
|
for (const key of ANTHROPIC_PARAMETER_KEYS) {
|
|
@@ -44,39 +53,8 @@ function collectParameters(payload) {
|
|
|
44
53
|
}
|
|
45
54
|
return Object.keys(params).length ? params : undefined;
|
|
46
55
|
}
|
|
47
|
-
function normalizeToolContent(content) {
|
|
48
|
-
if (typeof content === 'string')
|
|
49
|
-
return content;
|
|
50
|
-
if (content == null)
|
|
51
|
-
return '';
|
|
52
|
-
try {
|
|
53
|
-
return JSON.stringify(content);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return String(content ?? '');
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function collectToolOutputsFromMessages(messages, missing) {
|
|
60
|
-
const outputs = [];
|
|
61
|
-
messages.forEach((msg, index) => {
|
|
62
|
-
if (!msg || typeof msg !== 'object')
|
|
63
|
-
return;
|
|
64
|
-
if (msg.role !== 'tool')
|
|
65
|
-
return;
|
|
66
|
-
const callId = msg.tool_call_id || msg.id;
|
|
67
|
-
if (typeof callId !== 'string' || !callId.trim()) {
|
|
68
|
-
missing.push({ path: `messages[${index}].tool_call_id`, reason: 'missing_tool_call_id' });
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
outputs.push({
|
|
72
|
-
tool_call_id: callId.trim(),
|
|
73
|
-
content: normalizeToolContent(msg.content),
|
|
74
|
-
name: typeof msg.name === 'string' ? msg.name : undefined
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
return outputs.length ? outputs : undefined;
|
|
78
|
-
}
|
|
79
56
|
export class AnthropicSemanticMapper {
|
|
57
|
+
chatMapper = new ChatSemanticMapper();
|
|
80
58
|
async toChat(format, ctx) {
|
|
81
59
|
const payload = (format.payload ?? {});
|
|
82
60
|
const missing = [];
|
|
@@ -84,39 +62,109 @@ export class AnthropicSemanticMapper {
|
|
|
84
62
|
missing.push({ path: 'messages', reason: 'absent' });
|
|
85
63
|
if (typeof payload.model !== 'string')
|
|
86
64
|
missing.push({ path: 'model', reason: 'absent' });
|
|
87
|
-
const openaiPayload = buildOpenAIChatFromAnthropic(payload);
|
|
88
|
-
const messages = Array.isArray(openaiPayload.messages)
|
|
89
|
-
? openaiPayload.messages
|
|
90
|
-
: [];
|
|
91
|
-
const toolOutputs = collectToolOutputsFromMessages(messages, missing);
|
|
92
|
-
const tools = mapAnthropicToolsToChat(payload.tools, missing);
|
|
93
|
-
let parameters = collectParameters(payload);
|
|
94
65
|
const passthrough = extractMetadataPassthrough(payload.metadata, {
|
|
95
66
|
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
96
67
|
keys: PASSTHROUGH_PARAMETERS
|
|
97
68
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
69
|
+
const openaiPayload = buildOpenAIChatFromAnthropic(payload);
|
|
70
|
+
const canonicalContext = {
|
|
71
|
+
...ctx,
|
|
72
|
+
providerProtocol: 'openai-chat',
|
|
73
|
+
entryEndpoint: ctx.entryEndpoint || '/v1/chat/completions'
|
|
74
|
+
};
|
|
75
|
+
const chatEnvelope = await this.chatMapper.toChat({
|
|
76
|
+
protocol: 'openai-chat',
|
|
77
|
+
direction: 'request',
|
|
78
|
+
payload: openaiPayload
|
|
79
|
+
}, canonicalContext);
|
|
80
|
+
const metadata = chatEnvelope.metadata ?? { context: canonicalContext };
|
|
81
|
+
chatEnvelope.metadata = metadata;
|
|
82
|
+
metadata.context = canonicalContext;
|
|
83
|
+
const resolveExtraFields = () => {
|
|
84
|
+
if (!isJsonObject(metadata.extraFields)) {
|
|
85
|
+
metadata.extraFields = {};
|
|
86
|
+
}
|
|
87
|
+
return metadata.extraFields;
|
|
88
|
+
};
|
|
102
89
|
if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
|
|
103
90
|
metadata.toolsFieldPresent = true;
|
|
91
|
+
resolveExtraFields().toolsFieldPresent = true;
|
|
92
|
+
}
|
|
93
|
+
const aliasMap = buildAnthropicToolAliasMap(payload.tools);
|
|
94
|
+
if (aliasMap) {
|
|
95
|
+
const extraFields = resolveExtraFields();
|
|
96
|
+
ctx.anthropicToolNameMap = aliasMap;
|
|
97
|
+
canonicalContext.anthropicToolNameMap = aliasMap;
|
|
98
|
+
metadata.anthropicToolNameMap = aliasMap;
|
|
99
|
+
extraFields.anthropicToolNameMap = aliasMap;
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(payload.messages) && payload.messages.length) {
|
|
102
|
+
const shapes = payload.messages.map((entry) => {
|
|
103
|
+
if (!entry || typeof entry !== 'object') {
|
|
104
|
+
return 'unknown';
|
|
105
|
+
}
|
|
106
|
+
const rawContent = entry.content;
|
|
107
|
+
if (typeof rawContent === 'string') {
|
|
108
|
+
return 'string';
|
|
109
|
+
}
|
|
110
|
+
if (Array.isArray(rawContent)) {
|
|
111
|
+
return 'array';
|
|
112
|
+
}
|
|
113
|
+
if (rawContent === null || rawContent === undefined) {
|
|
114
|
+
return 'null';
|
|
115
|
+
}
|
|
116
|
+
return typeof rawContent;
|
|
117
|
+
});
|
|
118
|
+
const extraFields = resolveExtraFields();
|
|
119
|
+
const mirrorNode = extraFields.anthropicMirror && typeof extraFields.anthropicMirror === 'object'
|
|
120
|
+
? extraFields.anthropicMirror
|
|
121
|
+
: {};
|
|
122
|
+
mirrorNode.messageContentShape = shapes;
|
|
123
|
+
extraFields.anthropicMirror = mirrorNode;
|
|
104
124
|
}
|
|
105
125
|
if (missing.length) {
|
|
106
|
-
metadata.missingFields =
|
|
126
|
+
metadata.missingFields = Array.isArray(metadata.missingFields)
|
|
127
|
+
? [...metadata.missingFields, ...missing]
|
|
128
|
+
: missing;
|
|
107
129
|
}
|
|
108
|
-
|
|
109
|
-
metadata.
|
|
130
|
+
const providerMetadata = passthrough.metadata ??
|
|
131
|
+
(payload.metadata && isJsonObject(payload.metadata) ? jsonClone(payload.metadata) : undefined);
|
|
132
|
+
if (providerMetadata) {
|
|
133
|
+
metadata.providerMetadata = providerMetadata;
|
|
134
|
+
}
|
|
135
|
+
const mergedParameters = { ...(chatEnvelope.parameters ?? {}) };
|
|
136
|
+
const mergeParameters = (source) => {
|
|
137
|
+
if (!source) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
for (const [key, value] of Object.entries(source)) {
|
|
141
|
+
if (mergedParameters[key] !== undefined) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
mergedParameters[key] = jsonClone(value);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
mergeParameters(collectParameters(payload));
|
|
148
|
+
if (providerMetadata) {
|
|
149
|
+
mergedParameters.metadata = jsonClone(providerMetadata);
|
|
110
150
|
}
|
|
111
|
-
|
|
112
|
-
|
|
151
|
+
if (passthrough.passthrough) {
|
|
152
|
+
for (const [key, value] of Object.entries(passthrough.passthrough)) {
|
|
153
|
+
mergedParameters[key] = jsonClone(value);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (Object.keys(mergedParameters).length) {
|
|
157
|
+
chatEnvelope.parameters = mergedParameters;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
delete chatEnvelope.parameters;
|
|
113
161
|
}
|
|
114
162
|
try {
|
|
115
163
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
116
164
|
const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
|
|
117
165
|
if (actions?.length) {
|
|
118
166
|
const actionState = createBridgeActionState({
|
|
119
|
-
messages: messages,
|
|
167
|
+
messages: chatEnvelope.messages,
|
|
120
168
|
rawRequest: payload,
|
|
121
169
|
metadata: metadata
|
|
122
170
|
});
|
|
@@ -133,13 +181,7 @@ export class AnthropicSemanticMapper {
|
|
|
133
181
|
catch {
|
|
134
182
|
// best-effort metadata extraction
|
|
135
183
|
}
|
|
136
|
-
return
|
|
137
|
-
messages,
|
|
138
|
-
tools,
|
|
139
|
-
toolOutputs,
|
|
140
|
-
parameters,
|
|
141
|
-
metadata
|
|
142
|
-
};
|
|
184
|
+
return chatEnvelope;
|
|
143
185
|
}
|
|
144
186
|
async fromChat(chat, ctx) {
|
|
145
187
|
const model = chat.parameters?.model;
|
|
@@ -147,11 +189,21 @@ export class AnthropicSemanticMapper {
|
|
|
147
189
|
throw new Error('ChatEnvelope.parameters.model is required for anthropic-messages outbound conversion');
|
|
148
190
|
}
|
|
149
191
|
const baseRequest = {
|
|
150
|
-
...(chat.parameters || {}),
|
|
151
192
|
model,
|
|
152
193
|
messages: chat.messages,
|
|
153
194
|
tools: chat.tools
|
|
154
195
|
};
|
|
196
|
+
const trimmedParameters = chat.parameters && typeof chat.parameters === 'object' ? chat.parameters : undefined;
|
|
197
|
+
if (trimmedParameters) {
|
|
198
|
+
for (const [key, value] of Object.entries(trimmedParameters)) {
|
|
199
|
+
if (ANTHROPIC_TOP_LEVEL_FIELDS.has(key) || key === 'stop') {
|
|
200
|
+
if (key === 'messages' || key === 'tools') {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
baseRequest[key] = value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
155
207
|
const passthroughMetadata = encodeMetadataPassthrough(chat.parameters, {
|
|
156
208
|
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
157
209
|
keys: PASSTHROUGH_PARAMETERS
|
|
@@ -175,8 +227,15 @@ export class AnthropicSemanticMapper {
|
|
|
175
227
|
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
176
228
|
baseRequest.tools = [];
|
|
177
229
|
}
|
|
230
|
+
if (chat.metadata &&
|
|
231
|
+
typeof chat.metadata === 'object' &&
|
|
232
|
+
chat.metadata.extraFields &&
|
|
233
|
+
typeof chat.metadata.extraFields === 'object' &&
|
|
234
|
+
chat.metadata.extraFields.anthropicMirror) {
|
|
235
|
+
baseRequest.__anthropicMirror = jsonClone(chat.metadata.extraFields.anthropicMirror ?? {});
|
|
236
|
+
}
|
|
178
237
|
const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
|
|
179
|
-
const payload = JSON.parse(JSON.stringify(payloadSource));
|
|
238
|
+
const payload = sanitizeAnthropicPayload(JSON.parse(JSON.stringify(payloadSource)));
|
|
180
239
|
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
181
240
|
payload.tools = [];
|
|
182
241
|
}
|
|
@@ -210,6 +269,7 @@ export class AnthropicSemanticMapper {
|
|
|
210
269
|
catch {
|
|
211
270
|
// ignore metadata propagation failures
|
|
212
271
|
}
|
|
272
|
+
sanitizeAnthropicPayload(payload);
|
|
213
273
|
return {
|
|
214
274
|
protocol: 'anthropic-messages',
|
|
215
275
|
direction: 'response',
|