@jsonstudio/llms 0.4.4 → 0.4.6
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/codec-registry.js +11 -1
- package/dist/conversion/codecs/anthropic-openai-codec.d.ts +13 -0
- package/dist/conversion/codecs/anthropic-openai-codec.js +18 -473
- package/dist/conversion/codecs/gemini-openai-codec.js +91 -48
- package/dist/conversion/codecs/responses-openai-codec.js +9 -2
- package/dist/conversion/hub/format-adapters/anthropic-format-adapter.js +3 -0
- package/dist/conversion/hub/format-adapters/chat-format-adapter.js +3 -0
- package/dist/conversion/hub/format-adapters/gemini-format-adapter.js +3 -0
- package/dist/conversion/hub/format-adapters/responses-format-adapter.d.ts +19 -0
- package/dist/conversion/hub/format-adapters/responses-format-adapter.js +9 -0
- package/dist/conversion/hub/node-support.js +3 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +37 -32
- package/dist/conversion/hub/response/provider-response.js +1 -1
- package/dist/conversion/hub/response/response-mappers.js +1 -1
- package/dist/conversion/hub/response/response-runtime.js +109 -10
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +70 -156
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +63 -52
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +76 -143
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +40 -160
- package/dist/conversion/hub/standardized-bridge.js +3 -0
- package/dist/conversion/hub/tool-governance/rules.js +2 -2
- package/dist/conversion/index.d.ts +5 -0
- package/dist/conversion/index.js +5 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +12 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +100 -0
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.d.ts +15 -0
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +174 -0
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +14 -0
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +166 -0
- package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.d.ts +13 -0
- package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +66 -0
- package/dist/conversion/pipeline/hooks/adapter-context.d.ts +7 -0
- package/dist/conversion/pipeline/hooks/adapter-context.js +18 -0
- package/dist/conversion/pipeline/hooks/protocol-hooks.d.ts +67 -0
- package/dist/conversion/pipeline/hooks/protocol-hooks.js +1 -0
- package/dist/conversion/pipeline/index.d.ts +35 -0
- package/dist/conversion/pipeline/index.js +103 -0
- package/dist/conversion/pipeline/meta/meta-bag.d.ts +20 -0
- package/dist/conversion/pipeline/meta/meta-bag.js +81 -0
- package/dist/conversion/pipeline/schema/canonical-chat.d.ts +18 -0
- package/dist/conversion/pipeline/schema/canonical-chat.js +1 -0
- package/dist/conversion/pipeline/schema/index.d.ts +1 -0
- package/dist/conversion/pipeline/schema/index.js +1 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +48 -0
- package/dist/conversion/responses/responses-openai-bridge.js +157 -1146
- package/dist/conversion/shared/anthropic-message-utils.d.ts +12 -0
- package/dist/conversion/shared/anthropic-message-utils.js +587 -0
- package/dist/conversion/shared/bridge-actions.d.ts +39 -0
- package/dist/conversion/shared/bridge-actions.js +709 -0
- package/dist/conversion/shared/bridge-conversation-store.d.ts +41 -0
- package/dist/conversion/shared/bridge-conversation-store.js +279 -0
- package/dist/conversion/shared/bridge-id-utils.d.ts +7 -0
- package/dist/conversion/shared/bridge-id-utils.js +42 -0
- package/dist/conversion/shared/bridge-instructions.d.ts +1 -0
- package/dist/conversion/shared/bridge-instructions.js +113 -0
- package/dist/conversion/shared/bridge-message-types.d.ts +39 -0
- package/dist/conversion/shared/bridge-message-types.js +1 -0
- package/dist/conversion/shared/bridge-message-utils.d.ts +22 -0
- package/dist/conversion/shared/bridge-message-utils.js +473 -0
- package/dist/conversion/shared/bridge-metadata.d.ts +1 -0
- package/dist/conversion/shared/bridge-metadata.js +1 -0
- package/dist/conversion/shared/bridge-policies.d.ts +18 -0
- package/dist/conversion/shared/bridge-policies.js +276 -0
- package/dist/conversion/shared/bridge-request-adapter.d.ts +28 -0
- package/dist/conversion/shared/bridge-request-adapter.js +430 -0
- package/dist/conversion/shared/chat-output-normalizer.d.ts +4 -0
- package/dist/conversion/shared/chat-output-normalizer.js +56 -0
- package/dist/conversion/shared/chat-request-filters.js +24 -1
- package/dist/conversion/shared/gemini-tool-utils.d.ts +5 -0
- package/dist/conversion/shared/gemini-tool-utils.js +130 -0
- package/dist/conversion/shared/metadata-passthrough.d.ts +11 -0
- package/dist/conversion/shared/metadata-passthrough.js +57 -0
- package/dist/conversion/shared/output-content-normalizer.d.ts +12 -0
- package/dist/conversion/shared/output-content-normalizer.js +119 -0
- package/dist/conversion/shared/reasoning-normalizer.d.ts +21 -0
- package/dist/conversion/shared/reasoning-normalizer.js +368 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.d.ts +12 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.js +132 -0
- package/dist/conversion/shared/reasoning-tool-parser.d.ts +10 -0
- package/dist/conversion/shared/reasoning-tool-parser.js +95 -0
- package/dist/conversion/shared/reasoning-utils.d.ts +2 -0
- package/dist/conversion/shared/reasoning-utils.js +42 -0
- package/dist/conversion/shared/responses-conversation-store.js +5 -11
- package/dist/conversion/shared/responses-message-utils.d.ts +15 -0
- package/dist/conversion/shared/responses-message-utils.js +206 -0
- package/dist/conversion/shared/responses-output-builder.d.ts +15 -0
- package/dist/conversion/shared/responses-output-builder.js +179 -0
- package/dist/conversion/shared/responses-output-utils.d.ts +7 -0
- package/dist/conversion/shared/responses-output-utils.js +108 -0
- package/dist/conversion/shared/responses-request-adapter.d.ts +28 -0
- package/dist/conversion/shared/responses-request-adapter.js +9 -40
- package/dist/conversion/shared/responses-response-utils.d.ts +3 -0
- package/dist/conversion/shared/responses-response-utils.js +209 -0
- package/dist/conversion/shared/responses-tool-utils.d.ts +12 -0
- package/dist/conversion/shared/responses-tool-utils.js +90 -0
- package/dist/conversion/shared/responses-types.d.ts +33 -0
- package/dist/conversion/shared/responses-types.js +1 -0
- package/dist/conversion/shared/tool-call-utils.d.ts +11 -0
- package/dist/conversion/shared/tool-call-utils.js +56 -0
- package/dist/conversion/shared/tool-governor.js +5 -0
- package/dist/conversion/shared/tool-mapping.d.ts +19 -0
- package/dist/conversion/shared/tool-mapping.js +124 -0
- package/dist/conversion/shared/tool-normalizers.d.ts +4 -0
- package/dist/conversion/shared/tool-normalizers.js +84 -0
- package/dist/router/virtual-router/bootstrap.js +18 -3
- package/dist/router/virtual-router/provider-registry.js +4 -2
- package/dist/router/virtual-router/types.d.ts +212 -0
- package/dist/sse/index.d.ts +38 -2
- package/dist/sse/index.js +27 -0
- package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +14 -0
- package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.js +106 -73
- package/dist/sse/json-to-sse/chat-json-to-sse-converter.js +6 -2
- package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +14 -0
- package/dist/sse/json-to-sse/gemini-json-to-sse-converter.js +99 -0
- package/dist/sse/json-to-sse/index.d.ts +7 -0
- package/dist/sse/json-to-sse/index.js +2 -0
- package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +13 -0
- package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.js +150 -0
- package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +39 -0
- package/dist/sse/json-to-sse/sequencers/chat-sequencer.js +49 -3
- package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +10 -0
- package/dist/sse/json-to-sse/sequencers/gemini-sequencer.js +95 -0
- package/dist/sse/json-to-sse/sequencers/responses-sequencer.js +31 -5
- package/dist/sse/registry/sse-codec-registry.d.ts +32 -0
- package/dist/sse/registry/sse-codec-registry.js +30 -1
- package/dist/sse/shared/reasoning-dispatcher.d.ts +10 -0
- package/dist/sse/shared/reasoning-dispatcher.js +25 -0
- package/dist/sse/shared/responses-output-normalizer.d.ts +12 -0
- package/dist/sse/shared/responses-output-normalizer.js +45 -0
- package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +2 -0
- package/dist/sse/shared/serializers/anthropic-event-serializer.js +9 -0
- package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +2 -0
- package/dist/sse/shared/serializers/gemini-event-serializer.js +5 -0
- package/dist/sse/shared/serializers/index.d.ts +41 -0
- package/dist/sse/shared/serializers/index.js +2 -0
- package/dist/sse/shared/writer.d.ts +127 -0
- package/dist/sse/shared/writer.js +37 -1
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +11 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +92 -127
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +16 -0
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +151 -0
- package/dist/sse/sse-to-json/builders/response-builder.d.ts +165 -0
- package/dist/sse/sse-to-json/builders/response-builder.js +27 -6
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +114 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +79 -3
- package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +13 -0
- package/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +160 -0
- package/dist/sse/sse-to-json/index.d.ts +7 -0
- package/dist/sse/sse-to-json/index.js +2 -0
- package/dist/sse/sse-to-json/parsers/sse-parser.js +53 -1
- package/dist/sse/types/anthropic-types.d.ts +170 -0
- package/dist/sse/types/anthropic-types.js +8 -5
- package/dist/sse/types/chat-types.d.ts +10 -0
- package/dist/sse/types/chat-types.js +2 -1
- package/dist/sse/types/core-interfaces.d.ts +1 -1
- package/dist/sse/types/gemini-types.d.ts +116 -0
- package/dist/sse/types/gemini-types.js +5 -0
- package/dist/sse/types/index.d.ts +5 -2
- package/dist/sse/types/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatToolDefinition, MissingField } from '../hub/types/chat-envelope.js';
|
|
2
|
+
type Unknown = Record<string, unknown>;
|
|
3
|
+
type UnknownArray = Unknown[];
|
|
4
|
+
interface OpenAIChatPayload extends Unknown {
|
|
5
|
+
messages: UnknownArray;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildOpenAIChatFromAnthropic(payload: unknown): OpenAIChatPayload;
|
|
8
|
+
export declare function buildAnthropicFromOpenAIChat(oa: unknown): Unknown;
|
|
9
|
+
export declare function buildAnthropicRequestFromOpenAIChat(chatReq: unknown): Unknown;
|
|
10
|
+
export declare function mapAnthropicToolsToChat(rawTools: unknown, missing?: MissingField[]): ChatToolDefinition[] | undefined;
|
|
11
|
+
export declare function mapChatToolsToAnthropicTools(rawTools: unknown): UnknownArray | undefined;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import { createBridgeActionState, runBridgeActionPipeline } from './bridge-actions.js';
|
|
2
|
+
import { resolveBridgePolicy, resolvePolicyActions } from './bridge-policies.js';
|
|
3
|
+
import { normalizeChatMessageContent } from './chat-output-normalizer.js';
|
|
4
|
+
import { mapBridgeToolsToChat, mapChatToolsToBridge } from './tool-mapping.js';
|
|
5
|
+
import { jsonClone } from '../hub/types/json.js';
|
|
6
|
+
function isObject(v) {
|
|
7
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
8
|
+
}
|
|
9
|
+
function safeJson(v) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.stringify(v ?? {});
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return '{}';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function flattenAnthropicText(content) {
|
|
18
|
+
if (content == null)
|
|
19
|
+
return '';
|
|
20
|
+
if (typeof content === 'string')
|
|
21
|
+
return content;
|
|
22
|
+
if (Array.isArray(content))
|
|
23
|
+
return content.map(flattenAnthropicText).join('');
|
|
24
|
+
if (typeof content === 'object') {
|
|
25
|
+
const t = String(content.type || '').toLowerCase();
|
|
26
|
+
if (t === 'text' && typeof content.text === 'string')
|
|
27
|
+
return String(content.text);
|
|
28
|
+
if (Array.isArray(content.content))
|
|
29
|
+
return content.content.map(flattenAnthropicText).join('');
|
|
30
|
+
if (typeof content.content === 'string')
|
|
31
|
+
return String(content.content);
|
|
32
|
+
}
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
function requireTrimmedString(value, context) {
|
|
36
|
+
if (typeof value !== 'string') {
|
|
37
|
+
throw new Error(`Anthropic bridge constraint violated: ${context} must be a string`);
|
|
38
|
+
}
|
|
39
|
+
const trimmed = value.trim();
|
|
40
|
+
if (!trimmed.length) {
|
|
41
|
+
throw new Error(`Anthropic bridge constraint violated: ${context} must not be empty`);
|
|
42
|
+
}
|
|
43
|
+
return trimmed;
|
|
44
|
+
}
|
|
45
|
+
function requireSystemText(block, context) {
|
|
46
|
+
const text = flattenAnthropicText(block).trim();
|
|
47
|
+
if (!text) {
|
|
48
|
+
throw new Error(`Anthropic bridge constraint violated: ${context} must contain text`);
|
|
49
|
+
}
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
export function buildOpenAIChatFromAnthropic(payload) {
|
|
53
|
+
const newMessages = [];
|
|
54
|
+
const body = isObject(payload) ? payload : {};
|
|
55
|
+
const rawSystem = body.system;
|
|
56
|
+
const systemBlocks = Array.isArray(rawSystem)
|
|
57
|
+
? rawSystem
|
|
58
|
+
: rawSystem !== undefined && rawSystem !== null
|
|
59
|
+
? [rawSystem]
|
|
60
|
+
: [];
|
|
61
|
+
for (const block of systemBlocks) {
|
|
62
|
+
const text = requireSystemText(block, 'system entry');
|
|
63
|
+
newMessages.push({ role: 'system', content: text });
|
|
64
|
+
}
|
|
65
|
+
const msgs = Array.isArray(body.messages) ? body.messages : [];
|
|
66
|
+
for (const m of msgs) {
|
|
67
|
+
if (!m || typeof m !== 'object')
|
|
68
|
+
continue;
|
|
69
|
+
const role = typeof m.role === 'string' ? String(m.role) : 'user';
|
|
70
|
+
const content = m.content;
|
|
71
|
+
if (!Array.isArray(content)) {
|
|
72
|
+
const text = flattenAnthropicText(content);
|
|
73
|
+
if (text) {
|
|
74
|
+
const normalized = normalizeChatMessageContent(text);
|
|
75
|
+
const message = {
|
|
76
|
+
role,
|
|
77
|
+
content: normalized.contentText ?? text
|
|
78
|
+
};
|
|
79
|
+
if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
|
|
80
|
+
message.reasoning_content = normalized.reasoningText.trim();
|
|
81
|
+
}
|
|
82
|
+
newMessages.push(message);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const textParts = [];
|
|
87
|
+
const toolCalls = [];
|
|
88
|
+
const reasoningParts = [];
|
|
89
|
+
const toolResults = [];
|
|
90
|
+
for (const block of content) {
|
|
91
|
+
if (!block || typeof block !== 'object')
|
|
92
|
+
continue;
|
|
93
|
+
const t = String(block.type || '').toLowerCase();
|
|
94
|
+
if (t === 'text' && typeof block.text === 'string') {
|
|
95
|
+
const s = block.text.trim();
|
|
96
|
+
if (s)
|
|
97
|
+
textParts.push(s);
|
|
98
|
+
}
|
|
99
|
+
else if (t === 'thinking' || t === 'reasoning') {
|
|
100
|
+
const thinkingText = flattenAnthropicText(block).trim();
|
|
101
|
+
if (thinkingText) {
|
|
102
|
+
reasoningParts.push(thinkingText);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (t === 'tool_use') {
|
|
106
|
+
const name = requireTrimmedString(block.name, 'tool_use.name');
|
|
107
|
+
const id = requireTrimmedString(block.id, 'tool_use.id');
|
|
108
|
+
const input = block.input ?? {};
|
|
109
|
+
const args = safeJson(input);
|
|
110
|
+
toolCalls.push({ id, type: 'function', function: { name, arguments: args } });
|
|
111
|
+
}
|
|
112
|
+
else if (t === 'tool_result') {
|
|
113
|
+
const callId = requireTrimmedString(block.tool_call_id ??
|
|
114
|
+
block.call_id ??
|
|
115
|
+
block.tool_use_id ??
|
|
116
|
+
block.id, 'tool_result.tool_use_id');
|
|
117
|
+
let contentStr = '';
|
|
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
|
+
}
|
|
129
|
+
toolResults.push({ role: 'tool', tool_call_id: callId, content: contentStr });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const combinedText = textParts.join('\n');
|
|
133
|
+
const normalized = normalizeChatMessageContent(combinedText);
|
|
134
|
+
const mergedReasoning = [...reasoningParts];
|
|
135
|
+
if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
|
|
136
|
+
mergedReasoning.push(normalized.reasoningText.trim());
|
|
137
|
+
}
|
|
138
|
+
const hasText = typeof normalized.contentText === 'string' && normalized.contentText.length > 0;
|
|
139
|
+
const hasReasoning = mergedReasoning.length > 0;
|
|
140
|
+
if (hasText || toolCalls.length > 0 || hasReasoning) {
|
|
141
|
+
const msg = {
|
|
142
|
+
role,
|
|
143
|
+
content: normalized.contentText ?? combinedText ?? ''
|
|
144
|
+
};
|
|
145
|
+
if (toolCalls.length)
|
|
146
|
+
msg.tool_calls = toolCalls;
|
|
147
|
+
if (hasReasoning) {
|
|
148
|
+
msg.reasoning_content = mergedReasoning.join('\n');
|
|
149
|
+
}
|
|
150
|
+
newMessages.push(msg);
|
|
151
|
+
}
|
|
152
|
+
for (const tr of toolResults)
|
|
153
|
+
newMessages.push(tr);
|
|
154
|
+
}
|
|
155
|
+
const request = { messages: newMessages };
|
|
156
|
+
if (typeof body.model === 'string')
|
|
157
|
+
request.model = body.model;
|
|
158
|
+
if (typeof body.max_tokens === 'number')
|
|
159
|
+
request.max_tokens = body.max_tokens;
|
|
160
|
+
if (typeof body.temperature === 'number')
|
|
161
|
+
request.temperature = body.temperature;
|
|
162
|
+
if (typeof body.top_p === 'number')
|
|
163
|
+
request.top_p = body.top_p;
|
|
164
|
+
if (typeof body.stream === 'boolean')
|
|
165
|
+
request.stream = body.stream;
|
|
166
|
+
if ('tool_choice' in body)
|
|
167
|
+
request.tool_choice = body.tool_choice;
|
|
168
|
+
const normalizedTools = mapAnthropicToolsToChat(body.tools);
|
|
169
|
+
if (normalizedTools !== undefined) {
|
|
170
|
+
request.tools = normalizedTools;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
174
|
+
const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
|
|
175
|
+
if (actions?.length) {
|
|
176
|
+
const actionState = createBridgeActionState({
|
|
177
|
+
messages: newMessages,
|
|
178
|
+
rawRequest: body
|
|
179
|
+
});
|
|
180
|
+
runBridgeActionPipeline({
|
|
181
|
+
stage: 'request_inbound',
|
|
182
|
+
actions,
|
|
183
|
+
protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
|
|
184
|
+
moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
|
|
185
|
+
requestId: typeof body?.id === 'string' ? String(body.id) : undefined,
|
|
186
|
+
state: actionState
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// ignore policy failures
|
|
192
|
+
}
|
|
193
|
+
return request;
|
|
194
|
+
}
|
|
195
|
+
export function buildAnthropicFromOpenAIChat(oa) {
|
|
196
|
+
const mapFinishReason = (reason) => {
|
|
197
|
+
if (typeof reason !== 'string' || !reason.trim().length) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const mapping = {
|
|
201
|
+
stop: 'end_turn',
|
|
202
|
+
length: 'max_tokens',
|
|
203
|
+
tool_calls: 'tool_use',
|
|
204
|
+
content_filter: 'stop_sequence',
|
|
205
|
+
function_call: 'tool_use'
|
|
206
|
+
};
|
|
207
|
+
return mapping[reason.trim()];
|
|
208
|
+
};
|
|
209
|
+
const body = isObject(oa) ? oa : {};
|
|
210
|
+
const choices = Array.isArray(body.choices) ? body.choices : [];
|
|
211
|
+
const primary = choices[0] && typeof choices[0] === 'object' ? choices[0] : {};
|
|
212
|
+
const msg = isObject(primary.message) ? primary.message : {};
|
|
213
|
+
const role = typeof msg.role === 'string' ? msg.role : 'assistant';
|
|
214
|
+
const blocks = [];
|
|
215
|
+
const content = msg?.content;
|
|
216
|
+
const contentArray = Array.isArray(content) ? content : undefined;
|
|
217
|
+
if (typeof content === 'string') {
|
|
218
|
+
blocks.push({ type: 'text', text: content });
|
|
219
|
+
}
|
|
220
|
+
if (contentArray) {
|
|
221
|
+
const text = contentArray
|
|
222
|
+
.map((p) => p && typeof p.text === 'string'
|
|
223
|
+
? String(p.text)
|
|
224
|
+
: typeof p === 'string'
|
|
225
|
+
? p
|
|
226
|
+
: '')
|
|
227
|
+
.filter(Boolean)
|
|
228
|
+
.join('');
|
|
229
|
+
if (text) {
|
|
230
|
+
blocks.push({ type: 'text', text });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const reasoningField = typeof msg?.reasoning_content === 'string'
|
|
234
|
+
? msg.reasoning_content
|
|
235
|
+
: typeof msg?.reasoning === 'string'
|
|
236
|
+
? msg.reasoning
|
|
237
|
+
: undefined;
|
|
238
|
+
if (reasoningField && reasoningField.trim().length) {
|
|
239
|
+
blocks.push({ type: 'thinking', text: reasoningField.trim() });
|
|
240
|
+
}
|
|
241
|
+
const toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
|
|
242
|
+
for (const tc of toolCalls) {
|
|
243
|
+
try {
|
|
244
|
+
const id = requireTrimmedString(tc?.id, 'chat.tool_call.id');
|
|
245
|
+
const fn = isObject(tc?.function) ? tc.function : {};
|
|
246
|
+
const name = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
|
|
247
|
+
const argsRaw = fn.arguments;
|
|
248
|
+
let input;
|
|
249
|
+
if (typeof argsRaw === 'string') {
|
|
250
|
+
try {
|
|
251
|
+
input = JSON.parse(argsRaw);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
input = { _raw: argsRaw };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
input = argsRaw ?? {};
|
|
259
|
+
}
|
|
260
|
+
blocks.push({ type: 'tool_use', id, name, input });
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// ignore malformed tool call
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const usageChunk = isObject(body.usage) ? body.usage : {};
|
|
267
|
+
const inputTokens = Number(usageChunk.prompt_tokens ?? usageChunk.input_tokens ?? 0);
|
|
268
|
+
const outputTokens = Number(usageChunk.completion_tokens ?? usageChunk.output_tokens ?? 0);
|
|
269
|
+
const finishReason = Array.isArray(body.choices) &&
|
|
270
|
+
body.choices[0] &&
|
|
271
|
+
typeof body.choices[0]?.finish_reason === 'string'
|
|
272
|
+
? String(body.choices[0].finish_reason).trim()
|
|
273
|
+
: undefined;
|
|
274
|
+
const stopReason = mapFinishReason(finishReason);
|
|
275
|
+
return {
|
|
276
|
+
id: `resp_${Date.now()}`,
|
|
277
|
+
type: 'message',
|
|
278
|
+
role,
|
|
279
|
+
model: String(body.model || 'unknown'),
|
|
280
|
+
created: Math.floor(Date.now() / 1000),
|
|
281
|
+
content: blocks,
|
|
282
|
+
usage: inputTokens || outputTokens ? { input_tokens: inputTokens, output_tokens: outputTokens } : undefined,
|
|
283
|
+
...(stopReason ? { stop_reason: stopReason } : {})
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
export function buildAnthropicRequestFromOpenAIChat(chatReq) {
|
|
287
|
+
const requestBody = isObject(chatReq) ? chatReq : {};
|
|
288
|
+
const model = String(requestBody?.model || 'unknown');
|
|
289
|
+
const messages = [];
|
|
290
|
+
try {
|
|
291
|
+
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
292
|
+
const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
|
|
293
|
+
if (actions?.length && Array.isArray(requestBody.messages)) {
|
|
294
|
+
const actionState = createBridgeActionState({
|
|
295
|
+
messages: requestBody.messages,
|
|
296
|
+
rawRequest: requestBody
|
|
297
|
+
});
|
|
298
|
+
runBridgeActionPipeline({
|
|
299
|
+
stage: 'request_outbound',
|
|
300
|
+
actions,
|
|
301
|
+
protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
|
|
302
|
+
moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
|
|
303
|
+
state: actionState
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// ignore policy errors
|
|
309
|
+
}
|
|
310
|
+
const collectText = (val) => {
|
|
311
|
+
if (!val)
|
|
312
|
+
return '';
|
|
313
|
+
if (typeof val === 'string')
|
|
314
|
+
return val;
|
|
315
|
+
if (Array.isArray(val))
|
|
316
|
+
return val.map(collectText).join('');
|
|
317
|
+
if (typeof val === 'object') {
|
|
318
|
+
if (typeof val.text === 'string')
|
|
319
|
+
return String(val.text);
|
|
320
|
+
if (Array.isArray(val.content))
|
|
321
|
+
return collectText(val.content);
|
|
322
|
+
}
|
|
323
|
+
return '';
|
|
324
|
+
};
|
|
325
|
+
const msgs = Array.isArray(requestBody?.messages) ? requestBody.messages : [];
|
|
326
|
+
const knownToolCallIds = new Set();
|
|
327
|
+
for (const m of msgs) {
|
|
328
|
+
if (!m || typeof m !== 'object')
|
|
329
|
+
continue;
|
|
330
|
+
const role = String(m.role || 'user');
|
|
331
|
+
if (role !== 'assistant')
|
|
332
|
+
continue;
|
|
333
|
+
const toolCalls = m.tool_calls;
|
|
334
|
+
if (!Array.isArray(toolCalls))
|
|
335
|
+
continue;
|
|
336
|
+
for (const tc of toolCalls) {
|
|
337
|
+
if (!tc || typeof tc !== 'object')
|
|
338
|
+
continue;
|
|
339
|
+
const id = requireTrimmedString(tc.id, 'chat.tool_call.id');
|
|
340
|
+
knownToolCallIds.add(id);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const systemBlocks = [];
|
|
344
|
+
const pushSystemBlock = (text) => {
|
|
345
|
+
const trimmed = text.trim();
|
|
346
|
+
if (trimmed)
|
|
347
|
+
systemBlocks.push({ type: 'text', text: trimmed });
|
|
348
|
+
};
|
|
349
|
+
try {
|
|
350
|
+
const sys = requestBody.system;
|
|
351
|
+
const ingestSystem = (val) => {
|
|
352
|
+
if (!val)
|
|
353
|
+
return;
|
|
354
|
+
if (typeof val === 'string') {
|
|
355
|
+
pushSystemBlock(requireSystemText(val, 'top-level system'));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (Array.isArray(val)) {
|
|
359
|
+
for (const entry of val)
|
|
360
|
+
ingestSystem(entry);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (typeof val === 'object') {
|
|
364
|
+
pushSystemBlock(requireSystemText(val, 'top-level system'));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
throw new Error('Anthropic bridge constraint violated: unsupported system payload type');
|
|
368
|
+
};
|
|
369
|
+
ingestSystem(sys);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
// ignore system pre-scan errors
|
|
373
|
+
}
|
|
374
|
+
for (const m of msgs) {
|
|
375
|
+
if (!m || typeof m !== 'object')
|
|
376
|
+
continue;
|
|
377
|
+
const role = String(m.role || 'user');
|
|
378
|
+
const text = collectText(m.content).trim();
|
|
379
|
+
if (role === 'system') {
|
|
380
|
+
if (!text) {
|
|
381
|
+
throw new Error('Anthropic bridge constraint violated: Chat system message must contain text');
|
|
382
|
+
}
|
|
383
|
+
pushSystemBlock(text);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (role === 'tool') {
|
|
387
|
+
const toolCallId = requireTrimmedString(m.tool_call_id ?? m.call_id ?? m.tool_use_id ?? m.id, 'tool_result.tool_call_id');
|
|
388
|
+
if (!knownToolCallIds.has(toolCallId)) {
|
|
389
|
+
throw new Error(`Anthropic bridge constraint violated: tool result ${toolCallId} has no matching tool call`);
|
|
390
|
+
}
|
|
391
|
+
const block = {
|
|
392
|
+
type: 'tool_result',
|
|
393
|
+
content: text
|
|
394
|
+
};
|
|
395
|
+
block.tool_use_id = toolCallId;
|
|
396
|
+
messages.push({
|
|
397
|
+
role: 'user',
|
|
398
|
+
content: [block]
|
|
399
|
+
});
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const blocks = [];
|
|
403
|
+
if (text) {
|
|
404
|
+
blocks.push({ type: 'text', text });
|
|
405
|
+
}
|
|
406
|
+
const toolCalls = Array.isArray(m.tool_calls) ? m.tool_calls : [];
|
|
407
|
+
for (const tc of toolCalls) {
|
|
408
|
+
if (!tc || typeof tc !== 'object')
|
|
409
|
+
continue;
|
|
410
|
+
const id = requireTrimmedString(tc.id, 'chat.tool_call.id');
|
|
411
|
+
const fn = tc.function || {};
|
|
412
|
+
const name = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
|
|
413
|
+
const argsRaw = fn.arguments;
|
|
414
|
+
let input;
|
|
415
|
+
if (typeof argsRaw === 'string') {
|
|
416
|
+
try {
|
|
417
|
+
input = JSON.parse(argsRaw);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
input = { _raw: argsRaw };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
input = argsRaw ?? {};
|
|
425
|
+
}
|
|
426
|
+
blocks.push({ type: 'tool_use', id, name, input });
|
|
427
|
+
}
|
|
428
|
+
if (blocks.length > 0) {
|
|
429
|
+
messages.push({ role, content: blocks });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const out = { model };
|
|
433
|
+
if (systemBlocks.length) {
|
|
434
|
+
out.system = systemBlocks;
|
|
435
|
+
}
|
|
436
|
+
out.messages = messages;
|
|
437
|
+
const anthropicTools = mapChatToolsToAnthropicTools(requestBody.tools);
|
|
438
|
+
if (anthropicTools !== undefined) {
|
|
439
|
+
out.tools = anthropicTools;
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
if (requestBody.metadata && typeof requestBody.metadata === 'object') {
|
|
443
|
+
out.metadata = JSON.parse(JSON.stringify(requestBody.metadata));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// best-effort metadata clone
|
|
448
|
+
}
|
|
449
|
+
const mt = Number(requestBody.max_tokens ??
|
|
450
|
+
requestBody.maxTokens ??
|
|
451
|
+
NaN);
|
|
452
|
+
if (Number.isFinite(mt) && mt > 0)
|
|
453
|
+
out.max_tokens = mt;
|
|
454
|
+
if (typeof requestBody.temperature === 'number') {
|
|
455
|
+
out.temperature = Number(requestBody.temperature);
|
|
456
|
+
}
|
|
457
|
+
if (typeof requestBody.top_p === 'number') {
|
|
458
|
+
out.top_p = Number(requestBody.top_p);
|
|
459
|
+
}
|
|
460
|
+
if (typeof requestBody.stream === 'boolean') {
|
|
461
|
+
out.stream = Boolean(requestBody.stream);
|
|
462
|
+
}
|
|
463
|
+
const stop = requestBody.stop;
|
|
464
|
+
if (typeof stop === 'string' && stop.trim()) {
|
|
465
|
+
out.stop_sequences = [stop.trim()];
|
|
466
|
+
}
|
|
467
|
+
else if (Array.isArray(stop) && stop.length > 0) {
|
|
468
|
+
out.stop_sequences = stop.map((s) => String(s)).filter(Boolean);
|
|
469
|
+
}
|
|
470
|
+
return out;
|
|
471
|
+
}
|
|
472
|
+
function isPlainRecord(value) {
|
|
473
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
474
|
+
}
|
|
475
|
+
function coerceJsonValue(value) {
|
|
476
|
+
if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
if (Array.isArray(value)) {
|
|
480
|
+
return value.map((entry) => coerceJsonValue(entry));
|
|
481
|
+
}
|
|
482
|
+
if (isPlainRecord(value)) {
|
|
483
|
+
const obj = {};
|
|
484
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
485
|
+
obj[key] = coerceJsonValue(entry);
|
|
486
|
+
}
|
|
487
|
+
return obj;
|
|
488
|
+
}
|
|
489
|
+
return String(value ?? '');
|
|
490
|
+
}
|
|
491
|
+
function cloneAnthropicSchema(value) {
|
|
492
|
+
if (isPlainRecord(value)) {
|
|
493
|
+
try {
|
|
494
|
+
return JSON.parse(JSON.stringify(value));
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return value;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return { type: 'object', properties: {} };
|
|
501
|
+
}
|
|
502
|
+
function prepareAnthropicBridgeTools(rawTools, missing) {
|
|
503
|
+
if (!Array.isArray(rawTools) || rawTools.length === 0) {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
const result = [];
|
|
507
|
+
rawTools.forEach((entry, index) => {
|
|
508
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
509
|
+
missing?.push({
|
|
510
|
+
path: `tools[${index}]`,
|
|
511
|
+
reason: 'invalid_entry',
|
|
512
|
+
originalValue: jsonClone(coerceJsonValue(entry))
|
|
513
|
+
});
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const name = typeof entry.name === 'string'
|
|
517
|
+
? entry.name
|
|
518
|
+
: undefined;
|
|
519
|
+
if (!name) {
|
|
520
|
+
missing?.push({ path: `tools[${index}].name`, reason: 'missing_name' });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const description = typeof entry.description === 'string'
|
|
524
|
+
? entry.description
|
|
525
|
+
: undefined;
|
|
526
|
+
const schemaSource = entry.input_schema;
|
|
527
|
+
const parameters = cloneAnthropicSchema(schemaSource);
|
|
528
|
+
result.push({
|
|
529
|
+
type: 'function',
|
|
530
|
+
function: {
|
|
531
|
+
name,
|
|
532
|
+
description,
|
|
533
|
+
parameters
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
return result.length ? result : undefined;
|
|
538
|
+
}
|
|
539
|
+
function convertBridgeToolToAnthropic(def) {
|
|
540
|
+
if (!def || typeof def !== 'object') {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
const fnNode = def.function && typeof def.function === 'object' ? def.function : undefined;
|
|
544
|
+
const name = typeof fnNode?.name === 'string'
|
|
545
|
+
? fnNode.name
|
|
546
|
+
: typeof def.name === 'string'
|
|
547
|
+
? def.name
|
|
548
|
+
: undefined;
|
|
549
|
+
if (!name) {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
const description = typeof fnNode?.description === 'string'
|
|
553
|
+
? fnNode.description
|
|
554
|
+
: typeof def.description === 'string'
|
|
555
|
+
? def.description
|
|
556
|
+
: undefined;
|
|
557
|
+
const schemaSource = fnNode?.parameters ?? def.parameters;
|
|
558
|
+
const inputSchema = cloneAnthropicSchema(schemaSource);
|
|
559
|
+
const tool = {
|
|
560
|
+
name,
|
|
561
|
+
input_schema: inputSchema
|
|
562
|
+
};
|
|
563
|
+
if (description) {
|
|
564
|
+
tool.description = description;
|
|
565
|
+
}
|
|
566
|
+
return tool;
|
|
567
|
+
}
|
|
568
|
+
export function mapAnthropicToolsToChat(rawTools, missing) {
|
|
569
|
+
const prepared = prepareAnthropicBridgeTools(rawTools, missing);
|
|
570
|
+
if (prepared === undefined) {
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
return mapBridgeToolsToChat(prepared);
|
|
574
|
+
}
|
|
575
|
+
export function mapChatToolsToAnthropicTools(rawTools) {
|
|
576
|
+
if (!Array.isArray(rawTools) || rawTools.length === 0) {
|
|
577
|
+
return undefined;
|
|
578
|
+
}
|
|
579
|
+
const bridgeDefs = mapChatToolsToBridge(rawTools);
|
|
580
|
+
if (!bridgeDefs || !bridgeDefs.length) {
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
const converted = bridgeDefs
|
|
584
|
+
.map((def) => convertBridgeToolToAnthropic(def))
|
|
585
|
+
.filter((entry) => !!entry);
|
|
586
|
+
return converted.length ? converted : undefined;
|
|
587
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type UnknownRecord = Record<string, unknown>;
|
|
2
|
+
export type BridgeActionStage = 'request_inbound' | 'request_outbound' | 'response_inbound' | 'response_outbound';
|
|
3
|
+
export interface BridgeActionDescriptor {
|
|
4
|
+
name: string;
|
|
5
|
+
options?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface BridgeActionState {
|
|
8
|
+
messages: Array<UnknownRecord>;
|
|
9
|
+
requiredAction?: UnknownRecord;
|
|
10
|
+
capturedToolResults?: Array<{
|
|
11
|
+
tool_call_id?: string;
|
|
12
|
+
call_id?: string;
|
|
13
|
+
output?: unknown;
|
|
14
|
+
name?: string;
|
|
15
|
+
}>;
|
|
16
|
+
rawRequest?: UnknownRecord;
|
|
17
|
+
rawResponse?: UnknownRecord;
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export interface BridgeActionContext {
|
|
21
|
+
stage: BridgeActionStage;
|
|
22
|
+
protocol?: string;
|
|
23
|
+
moduleType?: string;
|
|
24
|
+
requestId?: string;
|
|
25
|
+
descriptor: BridgeActionDescriptor;
|
|
26
|
+
state: BridgeActionState;
|
|
27
|
+
}
|
|
28
|
+
export type BridgeAction = (context: BridgeActionContext) => void;
|
|
29
|
+
export declare function registerBridgeAction(name: string, action: BridgeAction): void;
|
|
30
|
+
export declare function createBridgeActionState(seed?: Partial<BridgeActionState>): BridgeActionState;
|
|
31
|
+
export declare function runBridgeActionPipeline(options: {
|
|
32
|
+
stage: BridgeActionStage;
|
|
33
|
+
actions?: BridgeActionDescriptor[];
|
|
34
|
+
protocol?: string;
|
|
35
|
+
moduleType?: string;
|
|
36
|
+
requestId?: string;
|
|
37
|
+
state: BridgeActionState;
|
|
38
|
+
}): void;
|
|
39
|
+
export {};
|