@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
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { JsonObject } from '../types/json.js';
|
|
2
|
+
type ToolAliasMap = Record<string, string>;
|
|
3
|
+
interface AnthropicResponseOptions {
|
|
4
|
+
aliasMap?: ToolAliasMap;
|
|
5
|
+
}
|
|
6
|
+
export declare function buildOpenAIChatFromAnthropicMessage(payload: JsonObject, options?: AnthropicResponseOptions): JsonObject;
|
|
7
|
+
export declare function buildAnthropicResponseFromChat(chatResponse: JsonObject, options?: AnthropicResponseOptions): JsonObject;
|
|
8
|
+
export {};
|
|
@@ -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,9 @@ 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';
|
|
8
|
+
import { ensureProtocolState, getProtocolState } from '../../shared/protocol-state.js';
|
|
7
9
|
const ANTHROPIC_PARAMETER_KEYS = [
|
|
8
10
|
'model',
|
|
9
11
|
'temperature',
|
|
@@ -32,6 +34,14 @@ const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
|
|
|
32
34
|
]);
|
|
33
35
|
const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
|
|
34
36
|
const PASSTHROUGH_PARAMETERS = ['tool_choice'];
|
|
37
|
+
function sanitizeAnthropicPayload(payload) {
|
|
38
|
+
for (const key of Object.keys(payload)) {
|
|
39
|
+
if (!ANTHROPIC_TOP_LEVEL_FIELDS.has(key)) {
|
|
40
|
+
delete payload[key];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return payload;
|
|
44
|
+
}
|
|
35
45
|
function collectParameters(payload) {
|
|
36
46
|
const params = {};
|
|
37
47
|
for (const key of ANTHROPIC_PARAMETER_KEYS) {
|
|
@@ -44,39 +54,18 @@ function collectParameters(payload) {
|
|
|
44
54
|
}
|
|
45
55
|
return Object.keys(params).length ? params : undefined;
|
|
46
56
|
}
|
|
47
|
-
function
|
|
48
|
-
if (
|
|
49
|
-
return
|
|
50
|
-
if (content == null)
|
|
51
|
-
return '';
|
|
52
|
-
try {
|
|
53
|
-
return JSON.stringify(content);
|
|
57
|
+
function cloneAnthropicSystemBlocks(value) {
|
|
58
|
+
if (value === undefined || value === null) {
|
|
59
|
+
return undefined;
|
|
54
60
|
}
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
const blocks = Array.isArray(value) ? value : [value];
|
|
62
|
+
if (!blocks.length) {
|
|
63
|
+
return undefined;
|
|
57
64
|
}
|
|
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;
|
|
65
|
+
return blocks.map((entry) => jsonClone(entry));
|
|
78
66
|
}
|
|
79
67
|
export class AnthropicSemanticMapper {
|
|
68
|
+
chatMapper = new ChatSemanticMapper();
|
|
80
69
|
async toChat(format, ctx) {
|
|
81
70
|
const payload = (format.payload ?? {});
|
|
82
71
|
const missing = [];
|
|
@@ -84,39 +73,114 @@ export class AnthropicSemanticMapper {
|
|
|
84
73
|
missing.push({ path: 'messages', reason: 'absent' });
|
|
85
74
|
if (typeof payload.model !== 'string')
|
|
86
75
|
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
76
|
const passthrough = extractMetadataPassthrough(payload.metadata, {
|
|
95
77
|
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
96
78
|
keys: PASSTHROUGH_PARAMETERS
|
|
97
79
|
});
|
|
98
|
-
|
|
99
|
-
|
|
80
|
+
const openaiPayload = buildOpenAIChatFromAnthropic(payload);
|
|
81
|
+
const canonicalContext = {
|
|
82
|
+
...ctx,
|
|
83
|
+
providerProtocol: 'openai-chat',
|
|
84
|
+
entryEndpoint: ctx.entryEndpoint || '/v1/chat/completions'
|
|
85
|
+
};
|
|
86
|
+
const chatEnvelope = await this.chatMapper.toChat({
|
|
87
|
+
protocol: 'openai-chat',
|
|
88
|
+
direction: 'request',
|
|
89
|
+
payload: openaiPayload
|
|
90
|
+
}, canonicalContext);
|
|
91
|
+
const metadata = chatEnvelope.metadata ?? { context: canonicalContext };
|
|
92
|
+
chatEnvelope.metadata = metadata;
|
|
93
|
+
metadata.context = canonicalContext;
|
|
94
|
+
const resolveExtraFields = () => {
|
|
95
|
+
if (!isJsonObject(metadata.extraFields)) {
|
|
96
|
+
metadata.extraFields = {};
|
|
97
|
+
}
|
|
98
|
+
return metadata.extraFields;
|
|
99
|
+
};
|
|
100
|
+
const protocolState = ensureProtocolState(metadata, 'anthropic');
|
|
101
|
+
const systemBlocks = cloneAnthropicSystemBlocks(payload.system);
|
|
102
|
+
if (systemBlocks) {
|
|
103
|
+
protocolState.systemBlocks = systemBlocks;
|
|
100
104
|
}
|
|
101
|
-
const metadata = { context: ctx };
|
|
102
105
|
if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
|
|
103
106
|
metadata.toolsFieldPresent = true;
|
|
107
|
+
resolveExtraFields().toolsFieldPresent = true;
|
|
108
|
+
}
|
|
109
|
+
const aliasMap = buildAnthropicToolAliasMap(payload.tools);
|
|
110
|
+
if (aliasMap) {
|
|
111
|
+
const extraFields = resolveExtraFields();
|
|
112
|
+
ctx.anthropicToolNameMap = aliasMap;
|
|
113
|
+
canonicalContext.anthropicToolNameMap = aliasMap;
|
|
114
|
+
metadata.anthropicToolNameMap = aliasMap;
|
|
115
|
+
extraFields.anthropicToolNameMap = aliasMap;
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(payload.messages) && payload.messages.length) {
|
|
118
|
+
const shapes = payload.messages.map((entry) => {
|
|
119
|
+
if (!entry || typeof entry !== 'object') {
|
|
120
|
+
return 'unknown';
|
|
121
|
+
}
|
|
122
|
+
const rawContent = entry.content;
|
|
123
|
+
if (typeof rawContent === 'string') {
|
|
124
|
+
return 'string';
|
|
125
|
+
}
|
|
126
|
+
if (Array.isArray(rawContent)) {
|
|
127
|
+
return 'array';
|
|
128
|
+
}
|
|
129
|
+
if (rawContent === null || rawContent === undefined) {
|
|
130
|
+
return 'null';
|
|
131
|
+
}
|
|
132
|
+
return typeof rawContent;
|
|
133
|
+
});
|
|
134
|
+
const extraFields = resolveExtraFields();
|
|
135
|
+
const mirrorNode = extraFields.anthropicMirror && typeof extraFields.anthropicMirror === 'object'
|
|
136
|
+
? extraFields.anthropicMirror
|
|
137
|
+
: {};
|
|
138
|
+
mirrorNode.messageContentShape = shapes;
|
|
139
|
+
extraFields.anthropicMirror = mirrorNode;
|
|
104
140
|
}
|
|
105
141
|
if (missing.length) {
|
|
106
|
-
metadata.missingFields =
|
|
142
|
+
metadata.missingFields = Array.isArray(metadata.missingFields)
|
|
143
|
+
? [...metadata.missingFields, ...missing]
|
|
144
|
+
: missing;
|
|
107
145
|
}
|
|
108
|
-
|
|
109
|
-
metadata.
|
|
146
|
+
const providerMetadata = passthrough.metadata ??
|
|
147
|
+
(payload.metadata && isJsonObject(payload.metadata) ? jsonClone(payload.metadata) : undefined);
|
|
148
|
+
if (providerMetadata) {
|
|
149
|
+
metadata.providerMetadata = providerMetadata;
|
|
110
150
|
}
|
|
111
|
-
|
|
112
|
-
|
|
151
|
+
const mergedParameters = { ...(chatEnvelope.parameters ?? {}) };
|
|
152
|
+
const mergeParameters = (source) => {
|
|
153
|
+
if (!source) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
for (const [key, value] of Object.entries(source)) {
|
|
157
|
+
if (mergedParameters[key] !== undefined) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
mergedParameters[key] = jsonClone(value);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
mergeParameters(collectParameters(payload));
|
|
164
|
+
if (providerMetadata) {
|
|
165
|
+
mergedParameters.metadata = jsonClone(providerMetadata);
|
|
166
|
+
}
|
|
167
|
+
if (passthrough.passthrough) {
|
|
168
|
+
for (const [key, value] of Object.entries(passthrough.passthrough)) {
|
|
169
|
+
mergedParameters[key] = jsonClone(value);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (Object.keys(mergedParameters).length) {
|
|
173
|
+
chatEnvelope.parameters = mergedParameters;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
delete chatEnvelope.parameters;
|
|
113
177
|
}
|
|
114
178
|
try {
|
|
115
179
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
116
180
|
const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
|
|
117
181
|
if (actions?.length) {
|
|
118
182
|
const actionState = createBridgeActionState({
|
|
119
|
-
messages: messages,
|
|
183
|
+
messages: chatEnvelope.messages,
|
|
120
184
|
rawRequest: payload,
|
|
121
185
|
metadata: metadata
|
|
122
186
|
});
|
|
@@ -133,13 +197,7 @@ export class AnthropicSemanticMapper {
|
|
|
133
197
|
catch {
|
|
134
198
|
// best-effort metadata extraction
|
|
135
199
|
}
|
|
136
|
-
return
|
|
137
|
-
messages,
|
|
138
|
-
tools,
|
|
139
|
-
toolOutputs,
|
|
140
|
-
parameters,
|
|
141
|
-
metadata
|
|
142
|
-
};
|
|
200
|
+
return chatEnvelope;
|
|
143
201
|
}
|
|
144
202
|
async fromChat(chat, ctx) {
|
|
145
203
|
const model = chat.parameters?.model;
|
|
@@ -147,11 +205,21 @@ export class AnthropicSemanticMapper {
|
|
|
147
205
|
throw new Error('ChatEnvelope.parameters.model is required for anthropic-messages outbound conversion');
|
|
148
206
|
}
|
|
149
207
|
const baseRequest = {
|
|
150
|
-
...(chat.parameters || {}),
|
|
151
208
|
model,
|
|
152
209
|
messages: chat.messages,
|
|
153
210
|
tools: chat.tools
|
|
154
211
|
};
|
|
212
|
+
const trimmedParameters = chat.parameters && typeof chat.parameters === 'object' ? chat.parameters : undefined;
|
|
213
|
+
if (trimmedParameters) {
|
|
214
|
+
for (const [key, value] of Object.entries(trimmedParameters)) {
|
|
215
|
+
if (ANTHROPIC_TOP_LEVEL_FIELDS.has(key) || key === 'stop') {
|
|
216
|
+
if (key === 'messages' || key === 'tools') {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
baseRequest[key] = value;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
155
223
|
const passthroughMetadata = encodeMetadataPassthrough(chat.parameters, {
|
|
156
224
|
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
157
225
|
keys: PASSTHROUGH_PARAMETERS
|
|
@@ -175,8 +243,19 @@ export class AnthropicSemanticMapper {
|
|
|
175
243
|
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
176
244
|
baseRequest.tools = [];
|
|
177
245
|
}
|
|
246
|
+
const protocolState = getProtocolState(chat.metadata, 'anthropic');
|
|
247
|
+
if (protocolState?.systemBlocks !== undefined) {
|
|
248
|
+
baseRequest.system = jsonClone(protocolState.systemBlocks);
|
|
249
|
+
}
|
|
250
|
+
if (chat.metadata &&
|
|
251
|
+
typeof chat.metadata === 'object' &&
|
|
252
|
+
chat.metadata.extraFields &&
|
|
253
|
+
typeof chat.metadata.extraFields === 'object' &&
|
|
254
|
+
chat.metadata.extraFields.anthropicMirror) {
|
|
255
|
+
baseRequest.__anthropicMirror = jsonClone(chat.metadata.extraFields.anthropicMirror ?? {});
|
|
256
|
+
}
|
|
178
257
|
const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
|
|
179
|
-
const payload = JSON.parse(JSON.stringify(payloadSource));
|
|
258
|
+
const payload = sanitizeAnthropicPayload(JSON.parse(JSON.stringify(payloadSource)));
|
|
180
259
|
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
181
260
|
payload.tools = [];
|
|
182
261
|
}
|
|
@@ -210,6 +289,7 @@ export class AnthropicSemanticMapper {
|
|
|
210
289
|
catch {
|
|
211
290
|
// ignore metadata propagation failures
|
|
212
291
|
}
|
|
292
|
+
sanitizeAnthropicPayload(payload);
|
|
213
293
|
return {
|
|
214
294
|
protocol: 'anthropic-messages',
|
|
215
295
|
direction: 'response',
|