@jsonstudio/llms 0.4.4 → 0.4.5
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-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
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { createBridgeActionState, runBridgeActionPipeline } from '../shared/bridge-actions.js';
|
|
2
|
+
import { resolveBridgePolicy, resolvePolicyActions } from '../shared/bridge-policies.js';
|
|
3
|
+
import { normalizeChatMessageContent } from '../shared/chat-output-normalizer.js';
|
|
4
|
+
import { mapBridgeToolsToChat } from '../shared/tool-mapping.js';
|
|
5
|
+
import { prepareGeminiToolsForBridge } from '../shared/gemini-tool-utils.js';
|
|
1
6
|
function isObject(v) {
|
|
2
7
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
3
8
|
}
|
|
@@ -48,49 +53,6 @@ function mapChatRoleToGemini(role) {
|
|
|
48
53
|
return 'tool';
|
|
49
54
|
return 'user';
|
|
50
55
|
}
|
|
51
|
-
function mapGeminiToolsToOpenAI(tools) {
|
|
52
|
-
const out = [];
|
|
53
|
-
if (!tools)
|
|
54
|
-
return out;
|
|
55
|
-
const arr = Array.isArray(tools) ? tools : [tools];
|
|
56
|
-
for (const t of arr) {
|
|
57
|
-
if (!t || typeof t !== 'object')
|
|
58
|
-
continue;
|
|
59
|
-
const tObj = t;
|
|
60
|
-
const fds = Array.isArray(tObj.functionDeclarations) ? tObj.functionDeclarations : [];
|
|
61
|
-
if (fds.length) {
|
|
62
|
-
for (const fd of fds) {
|
|
63
|
-
if (!fd || typeof fd !== 'object')
|
|
64
|
-
continue;
|
|
65
|
-
const name = typeof fd.name === 'string' ? String(fd.name) : undefined;
|
|
66
|
-
if (!name)
|
|
67
|
-
continue;
|
|
68
|
-
const description = typeof fd.description === 'string' ? String(fd.description) : undefined;
|
|
69
|
-
const parameters = isObject(fd.parameters)
|
|
70
|
-
? JSON.parse(JSON.stringify(fd.parameters))
|
|
71
|
-
: { type: 'object', properties: {} };
|
|
72
|
-
const fn = { name, parameters };
|
|
73
|
-
if (description)
|
|
74
|
-
fn.description = description;
|
|
75
|
-
out.push({ type: 'function', function: fn });
|
|
76
|
-
}
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
// 单个函数声明形状:直接把对象视为 FunctionDeclaration
|
|
80
|
-
const name = typeof tObj.name === 'string' ? String(tObj.name) : undefined;
|
|
81
|
-
if (name) {
|
|
82
|
-
const description = typeof tObj.description === 'string' ? String(tObj.description) : undefined;
|
|
83
|
-
const parameters = isObject(tObj.parameters)
|
|
84
|
-
? JSON.parse(JSON.stringify(tObj.parameters))
|
|
85
|
-
: { type: 'object', properties: {} };
|
|
86
|
-
const fn = { name, parameters };
|
|
87
|
-
if (description)
|
|
88
|
-
fn.description = description;
|
|
89
|
-
out.push({ type: 'function', function: fn });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return out;
|
|
93
|
-
}
|
|
94
56
|
export function buildOpenAIChatFromGeminiRequest(payload) {
|
|
95
57
|
const messages = [];
|
|
96
58
|
// systemInstruction → Chat system 消息
|
|
@@ -160,8 +122,21 @@ export function buildOpenAIChatFromGeminiRequest(payload) {
|
|
|
160
122
|
continue;
|
|
161
123
|
}
|
|
162
124
|
}
|
|
163
|
-
|
|
164
|
-
|
|
125
|
+
const combinedText = textParts.join('\n');
|
|
126
|
+
const normalized = combinedText.length ? normalizeChatMessageContent(combinedText) : { contentText: undefined, reasoningText: undefined };
|
|
127
|
+
const hasText = typeof normalized.contentText === 'string' && normalized.contentText.length > 0;
|
|
128
|
+
const reasoningChunks = [];
|
|
129
|
+
if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
|
|
130
|
+
reasoningChunks.push(normalized.reasoningText.trim());
|
|
131
|
+
}
|
|
132
|
+
if (hasText || toolCalls.length > 0 || reasoningChunks.length > 0) {
|
|
133
|
+
const msg = {
|
|
134
|
+
role,
|
|
135
|
+
content: normalized.contentText ?? combinedText ?? ''
|
|
136
|
+
};
|
|
137
|
+
if (reasoningChunks.length) {
|
|
138
|
+
msg.reasoning_content = reasoningChunks.join('\n');
|
|
139
|
+
}
|
|
165
140
|
if (toolCalls.length)
|
|
166
141
|
msg.tool_calls = toolCalls;
|
|
167
142
|
messages.push(msg);
|
|
@@ -178,6 +153,7 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
178
153
|
const role = mapGeminiRoleToChat(content.role);
|
|
179
154
|
const parts = Array.isArray(content.parts) ? content.parts : [];
|
|
180
155
|
const textParts = [];
|
|
156
|
+
const reasoningParts = [];
|
|
181
157
|
const toolCalls = [];
|
|
182
158
|
for (const part of parts) {
|
|
183
159
|
if (!part || typeof part !== 'object')
|
|
@@ -189,6 +165,21 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
189
165
|
textParts.push(t);
|
|
190
166
|
continue;
|
|
191
167
|
}
|
|
168
|
+
if (Array.isArray(pObj.content)) {
|
|
169
|
+
for (const inner of pObj.content) {
|
|
170
|
+
if (typeof inner === 'string') {
|
|
171
|
+
textParts.push(inner);
|
|
172
|
+
}
|
|
173
|
+
else if (inner && typeof inner === 'object' && typeof inner.text === 'string') {
|
|
174
|
+
textParts.push(inner.text);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (typeof pObj.reasoning === 'string') {
|
|
180
|
+
reasoningParts.push(pObj.reasoning);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
192
183
|
if (pObj.functionCall && typeof pObj.functionCall === 'object') {
|
|
193
184
|
const fc = pObj.functionCall;
|
|
194
185
|
const name = typeof fc.name === 'string' ? String(fc.name) : undefined;
|
|
@@ -234,13 +225,42 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
234
225
|
usage.completion_tokens = completionTokens;
|
|
235
226
|
if (Number.isFinite(totalTokens))
|
|
236
227
|
usage.total_tokens = totalTokens;
|
|
228
|
+
const combinedText = textParts.join('\n');
|
|
229
|
+
const normalized = combinedText.length ? normalizeChatMessageContent(combinedText) : { contentText: undefined, reasoningText: undefined };
|
|
237
230
|
const chatMsg = {
|
|
238
231
|
role,
|
|
239
|
-
content:
|
|
232
|
+
content: normalized.contentText ?? combinedText ?? ''
|
|
240
233
|
};
|
|
234
|
+
if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
|
|
235
|
+
reasoningParts.push(normalized.reasoningText.trim());
|
|
236
|
+
}
|
|
237
|
+
if (reasoningParts.length) {
|
|
238
|
+
chatMsg.reasoning_content = reasoningParts.join('\n');
|
|
239
|
+
}
|
|
241
240
|
if (toolCalls.length) {
|
|
242
241
|
chatMsg.tool_calls = toolCalls;
|
|
243
242
|
}
|
|
243
|
+
try {
|
|
244
|
+
const bridgePolicy = resolveBridgePolicy({ protocol: 'gemini-chat' });
|
|
245
|
+
const actions = resolvePolicyActions(bridgePolicy, 'response_inbound');
|
|
246
|
+
if (actions?.length) {
|
|
247
|
+
const actionState = createBridgeActionState({
|
|
248
|
+
messages: [chatMsg],
|
|
249
|
+
rawResponse: isObject(payload) ? payload : undefined
|
|
250
|
+
});
|
|
251
|
+
runBridgeActionPipeline({
|
|
252
|
+
stage: 'response_inbound',
|
|
253
|
+
actions,
|
|
254
|
+
protocol: bridgePolicy?.protocol ?? 'gemini-chat',
|
|
255
|
+
moduleType: bridgePolicy?.moduleType ?? 'gemini-chat',
|
|
256
|
+
requestId: typeof payload?.id === 'string' ? String(payload.id) : undefined,
|
|
257
|
+
state: actionState
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// best-effort policy execution
|
|
263
|
+
}
|
|
244
264
|
const chatResp = {
|
|
245
265
|
id: payload?.id || `chatcmpl_${Date.now()}`,
|
|
246
266
|
object: 'chat.completion',
|
|
@@ -276,6 +296,28 @@ export function buildGeminiFromOpenAIChat(chatResp) {
|
|
|
276
296
|
return 'STOP';
|
|
277
297
|
})();
|
|
278
298
|
const baseRole = mapChatRoleToGemini(msg.role || 'assistant');
|
|
299
|
+
if (msg) {
|
|
300
|
+
try {
|
|
301
|
+
const bridgePolicy = resolveBridgePolicy({ protocol: 'gemini-chat' });
|
|
302
|
+
const actions = resolvePolicyActions(bridgePolicy, 'response_outbound');
|
|
303
|
+
if (actions?.length) {
|
|
304
|
+
const actionState = createBridgeActionState({
|
|
305
|
+
messages: [msg]
|
|
306
|
+
});
|
|
307
|
+
runBridgeActionPipeline({
|
|
308
|
+
stage: 'response_outbound',
|
|
309
|
+
actions,
|
|
310
|
+
protocol: bridgePolicy?.protocol ?? 'gemini-chat',
|
|
311
|
+
moduleType: bridgePolicy?.moduleType ?? 'gemini-chat',
|
|
312
|
+
requestId: typeof chatResp?.id === 'string' ? String(chatResp.id) : undefined,
|
|
313
|
+
state: actionState
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// ignore policy failures
|
|
319
|
+
}
|
|
320
|
+
}
|
|
279
321
|
const parts = [];
|
|
280
322
|
const contentText = (() => {
|
|
281
323
|
const raw = msg.content;
|
|
@@ -377,8 +419,9 @@ export class GeminiOpenAIConversionCodec {
|
|
|
377
419
|
const { messages } = buildOpenAIChatFromGeminiRequest(payload);
|
|
378
420
|
const out = { model, messages };
|
|
379
421
|
try {
|
|
380
|
-
const
|
|
381
|
-
|
|
422
|
+
const bridgeTools = prepareGeminiToolsForBridge(payload?.tools);
|
|
423
|
+
const tools = bridgeTools ? mapBridgeToolsToChat(bridgeTools) : undefined;
|
|
424
|
+
if (tools && tools.length > 0)
|
|
382
425
|
out.tools = tools;
|
|
383
426
|
}
|
|
384
427
|
catch {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildResponsesPayloadFromChat, runStandardChatRequestFilters } from '../index.js';
|
|
2
|
-
import { captureResponsesContext, buildChatRequestFromResponses } from '../
|
|
2
|
+
import { captureResponsesContext, buildChatRequestFromResponses } from '../responses/responses-openai-bridge.js';
|
|
3
3
|
import { FilterEngine, ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } from '../../filters/index.js';
|
|
4
4
|
// Ported from root package (no behavior change). Types relaxed.
|
|
5
5
|
export class ResponsesOpenAIConversionCodec {
|
|
@@ -82,7 +82,14 @@ export class ResponsesOpenAIConversionCodec {
|
|
|
82
82
|
entryEndpoint: context.entryEndpoint ?? dto.metadata?.entryEndpoint ?? context.endpoint,
|
|
83
83
|
endpoint: context.endpoint ?? dto.metadata?.endpoint
|
|
84
84
|
};
|
|
85
|
-
|
|
85
|
+
const filtered = await runStandardChatRequestFilters(chatRequest, profile, ctxForFilters);
|
|
86
|
+
if (filtered && typeof filtered === 'object') {
|
|
87
|
+
const maybe = filtered;
|
|
88
|
+
if (maybe.max_tokens === undefined && typeof maybe.max_output_tokens === 'number') {
|
|
89
|
+
maybe.max_tokens = maybe.max_output_tokens;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return filtered;
|
|
86
93
|
}
|
|
87
94
|
async convertResponse(payload, _profile, context) {
|
|
88
95
|
await this.ensureInit();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { normalizeReasoningInAnthropicPayload } from '../../shared/reasoning-normalizer.js';
|
|
1
2
|
export class AnthropicFormatAdapter {
|
|
2
3
|
protocol = 'anthropic-messages';
|
|
3
4
|
async parseRequest(original, _ctx) {
|
|
5
|
+
normalizeReasoningInAnthropicPayload(original);
|
|
4
6
|
return {
|
|
5
7
|
protocol: this.protocol,
|
|
6
8
|
direction: 'request',
|
|
@@ -11,6 +13,7 @@ export class AnthropicFormatAdapter {
|
|
|
11
13
|
return format.payload;
|
|
12
14
|
}
|
|
13
15
|
async parseResponse(original, _ctx) {
|
|
16
|
+
normalizeReasoningInAnthropicPayload(original);
|
|
14
17
|
return {
|
|
15
18
|
protocol: this.protocol,
|
|
16
19
|
direction: 'response',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { normalizeReasoningInChatPayload } from '../../shared/reasoning-normalizer.js';
|
|
1
2
|
export class ChatFormatAdapter {
|
|
2
3
|
protocol = 'openai-chat';
|
|
3
4
|
async parseRequest(original, _ctx) {
|
|
5
|
+
normalizeReasoningInChatPayload(original);
|
|
4
6
|
return {
|
|
5
7
|
protocol: this.protocol,
|
|
6
8
|
direction: 'request',
|
|
@@ -11,6 +13,7 @@ export class ChatFormatAdapter {
|
|
|
11
13
|
return format.payload;
|
|
12
14
|
}
|
|
13
15
|
async parseResponse(original, _ctx) {
|
|
16
|
+
normalizeReasoningInChatPayload(original);
|
|
14
17
|
return {
|
|
15
18
|
protocol: this.protocol,
|
|
16
19
|
direction: 'response',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { normalizeReasoningInGeminiPayload } from '../../shared/reasoning-normalizer.js';
|
|
1
2
|
export class GeminiFormatAdapter {
|
|
2
3
|
protocol = 'gemini-chat';
|
|
3
4
|
async parseRequest(original, _ctx) {
|
|
5
|
+
normalizeReasoningInGeminiPayload(original);
|
|
4
6
|
return {
|
|
5
7
|
protocol: this.protocol,
|
|
6
8
|
direction: 'request',
|
|
@@ -11,6 +13,7 @@ export class GeminiFormatAdapter {
|
|
|
11
13
|
return format.payload;
|
|
12
14
|
}
|
|
13
15
|
async parseResponse(original, _ctx) {
|
|
16
|
+
normalizeReasoningInGeminiPayload(original);
|
|
14
17
|
return {
|
|
15
18
|
protocol: this.protocol,
|
|
16
19
|
direction: 'response',
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FormatAdapter } from './index.js';
|
|
2
|
+
import type { AdapterContext } from '../types/chat-envelope.js';
|
|
3
|
+
import type { FormatEnvelope } from '../types/format-envelope.js';
|
|
4
|
+
import type { JsonObject, JsonValue } from '../types/json.js';
|
|
5
|
+
interface ResponsesFormatPayload extends JsonObject {
|
|
6
|
+
input?: JsonValue[];
|
|
7
|
+
tools?: JsonValue[];
|
|
8
|
+
tool_outputs?: JsonValue[];
|
|
9
|
+
output?: JsonValue[];
|
|
10
|
+
id?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class ResponsesFormatAdapter implements FormatAdapter {
|
|
13
|
+
readonly protocol = "openai-responses";
|
|
14
|
+
parseRequest(original: JsonObject, _ctx: AdapterContext): Promise<FormatEnvelope<ResponsesFormatPayload>>;
|
|
15
|
+
buildRequest(format: FormatEnvelope<ResponsesFormatPayload>, _ctx: AdapterContext): Promise<ResponsesFormatPayload>;
|
|
16
|
+
parseResponse(original: JsonObject, _ctx: AdapterContext): Promise<FormatEnvelope<ResponsesFormatPayload>>;
|
|
17
|
+
buildResponse(format: FormatEnvelope<ResponsesFormatPayload>, _ctx: AdapterContext): Promise<ResponsesFormatPayload>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { normalizeReasoningInResponsesPayload } from '../../shared/reasoning-normalizer.js';
|
|
1
2
|
export class ResponsesFormatAdapter {
|
|
2
3
|
protocol = 'openai-responses';
|
|
3
4
|
async parseRequest(original, _ctx) {
|
|
5
|
+
normalizeReasoningInResponsesPayload(original, {
|
|
6
|
+
includeInput: true,
|
|
7
|
+
includeInstructions: true
|
|
8
|
+
});
|
|
4
9
|
return {
|
|
5
10
|
protocol: this.protocol,
|
|
6
11
|
direction: 'request',
|
|
@@ -11,6 +16,10 @@ export class ResponsesFormatAdapter {
|
|
|
11
16
|
return format.payload;
|
|
12
17
|
}
|
|
13
18
|
async parseResponse(original, _ctx) {
|
|
19
|
+
normalizeReasoningInResponsesPayload(original, {
|
|
20
|
+
includeOutput: true,
|
|
21
|
+
includeRequiredAction: true
|
|
22
|
+
});
|
|
14
23
|
return {
|
|
15
24
|
protocol: this.protocol,
|
|
16
25
|
direction: 'response',
|
|
@@ -102,6 +102,7 @@ function deriveAdapterContext(context, fallbackProtocol) {
|
|
|
102
102
|
(typeof metadata.providerProtocol === 'string' ? metadata.providerProtocol : undefined) ||
|
|
103
103
|
fallbackProtocol;
|
|
104
104
|
const streamingHint = metadata.stream === true ? 'force' : metadata.stream === false ? 'disable' : 'auto';
|
|
105
|
+
const toolCallIdStyle = typeof metadata.toolCallIdStyle === 'string' ? metadata.toolCallIdStyle : undefined;
|
|
105
106
|
return {
|
|
106
107
|
requestId: context.request.id,
|
|
107
108
|
entryEndpoint: (typeof requestContext.entryEndpoint === 'string' ? requestContext.entryEndpoint : context.request.endpoint) ||
|
|
@@ -110,6 +111,7 @@ function deriveAdapterContext(context, fallbackProtocol) {
|
|
|
110
111
|
providerId: (target?.providerKey || metadata.providerKey),
|
|
111
112
|
routeId: metadata.routeName,
|
|
112
113
|
profileId: metadata.pipelineId,
|
|
113
|
-
streamingHint
|
|
114
|
+
streamingHint,
|
|
115
|
+
toolCallIdStyle
|
|
114
116
|
};
|
|
115
117
|
}
|
|
@@ -121,6 +121,9 @@ export class HubPipeline {
|
|
|
121
121
|
metadata.providerType = target.providerType;
|
|
122
122
|
metadata.modelId = target.modelId;
|
|
123
123
|
metadata.processMode = target.processMode || 'chat';
|
|
124
|
+
if (target.responsesConfig?.toolCallIdStyle) {
|
|
125
|
+
metadata.toolCallIdStyle = target.responsesConfig.toolCallIdStyle;
|
|
126
|
+
}
|
|
124
127
|
if (originalModel && typeof originalModel === 'string' && originalModel.trim()) {
|
|
125
128
|
const trimmed = originalModel.trim();
|
|
126
129
|
if (typeof metadata.originalModelId !== 'string' || !metadata.originalModelId) {
|
|
@@ -237,9 +240,7 @@ export class HubPipeline {
|
|
|
237
240
|
const entryEndpoint = typeof metadataRecord.entryEndpoint === 'string'
|
|
238
241
|
? normalizeEndpoint(metadataRecord.entryEndpoint)
|
|
239
242
|
: endpoint;
|
|
240
|
-
const providerProtocol =
|
|
241
|
-
? metadataRecord.providerProtocol
|
|
242
|
-
: inferProviderProtocol(entryEndpoint);
|
|
243
|
+
const providerProtocol = resolveProviderProtocol(metadataRecord.providerProtocol);
|
|
243
244
|
const processMode = metadataRecord.processMode === 'passthrough' ? 'passthrough' : 'chat';
|
|
244
245
|
const direction = metadataRecord.direction === 'response' ? 'response' : 'request';
|
|
245
246
|
const stage = metadataRecord.stage === 'outbound' ? 'outbound' : 'inbound';
|
|
@@ -358,15 +359,11 @@ export class HubPipeline {
|
|
|
358
359
|
}
|
|
359
360
|
}
|
|
360
361
|
resolveSseProtocol(context) {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const routeHint = typeof context.metadata.routeHint === 'string'
|
|
367
|
-
? context.metadata.routeHint
|
|
368
|
-
: undefined;
|
|
369
|
-
return inferSseProtocol(routeHint, providerProtocol, context.entryEndpoint);
|
|
362
|
+
const explicitProtocol = resolveSseProtocolFromMetadata(context.metadata);
|
|
363
|
+
if (explicitProtocol) {
|
|
364
|
+
return explicitProtocol;
|
|
365
|
+
}
|
|
366
|
+
return context.providerProtocol;
|
|
370
367
|
}
|
|
371
368
|
extractModelHint(metadata) {
|
|
372
369
|
if (typeof metadata.model === 'string' && metadata.model.trim()) {
|
|
@@ -395,26 +392,34 @@ function normalizeEndpoint(endpoint) {
|
|
|
395
392
|
const normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
396
393
|
return normalized.replace(/\/{2,}/g, '/');
|
|
397
394
|
}
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
return
|
|
406
|
-
|
|
395
|
+
function resolveProviderProtocol(value) {
|
|
396
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
397
|
+
return 'openai-chat';
|
|
398
|
+
}
|
|
399
|
+
const normalized = value.trim().toLowerCase();
|
|
400
|
+
const mapped = PROVIDER_PROTOCOL_ALIASES[normalized];
|
|
401
|
+
if (mapped) {
|
|
402
|
+
return mapped;
|
|
403
|
+
}
|
|
404
|
+
throw new Error(`[HubPipeline] Unsupported providerProtocol "${value}". Configure a valid protocol (openai-chat|openai-responses|anthropic-messages|gemini-chat).`);
|
|
407
405
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
406
|
+
const PROVIDER_PROTOCOL_ALIASES = {
|
|
407
|
+
'openai-chat': 'openai-chat',
|
|
408
|
+
openai: 'openai-chat',
|
|
409
|
+
chat: 'openai-chat',
|
|
410
|
+
'responses': 'openai-responses',
|
|
411
|
+
'openai-responses': 'openai-responses',
|
|
412
|
+
'anthropic': 'anthropic-messages',
|
|
413
|
+
'anthropic-messages': 'anthropic-messages',
|
|
414
|
+
'messages': 'anthropic-messages',
|
|
415
|
+
'gemini': 'gemini-chat',
|
|
416
|
+
'google-gemini': 'gemini-chat',
|
|
417
|
+
'gemini-chat': 'gemini-chat'
|
|
418
|
+
};
|
|
419
|
+
function resolveSseProtocolFromMetadata(metadata) {
|
|
420
|
+
const candidate = metadata.sseProtocol ?? metadata.clientSseProtocol ?? metadata.routeSseProtocol;
|
|
421
|
+
if (typeof candidate !== 'string' || !candidate.trim()) {
|
|
422
|
+
return undefined;
|
|
418
423
|
}
|
|
419
|
-
return
|
|
424
|
+
return resolveProviderProtocol(candidate);
|
|
420
425
|
}
|
|
@@ -89,7 +89,7 @@ async function maybeCreateSseStream(protocol, payload, requestId) {
|
|
|
89
89
|
return codec.convertJsonToSse(payload, { requestId });
|
|
90
90
|
}
|
|
91
91
|
function supportsSseProtocol(protocol) {
|
|
92
|
-
return protocol === 'openai-chat' || protocol === 'openai-responses' || protocol === 'anthropic-messages';
|
|
92
|
+
return protocol === 'openai-chat' || protocol === 'openai-responses' || protocol === 'anthropic-messages' || protocol === 'gemini-chat';
|
|
93
93
|
}
|
|
94
94
|
function extractDisplayModel(context) {
|
|
95
95
|
const candidates = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildOpenAIChatFromGeminiResponse } from '../../codecs/gemini-openai-codec.js';
|
|
2
|
-
import { buildChatResponseFromResponses } from '../../
|
|
2
|
+
import { buildChatResponseFromResponses } from '../../shared/responses-response-utils.js';
|
|
3
3
|
import { buildOpenAIChatFromAnthropicMessage } from './response-runtime.js';
|
|
4
4
|
export class OpenAIChatResponseMapper {
|
|
5
5
|
toChatCompletion(format, _ctx) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { extractToolCallsFromReasoningText } from '../../shared/reasoning-tool-parser.js';
|
|
2
|
+
import { deriveToolCallKey } from '../../shared/tool-call-utils.js';
|
|
3
|
+
import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
|
|
4
|
+
import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
|
|
1
5
|
function flattenAnthropicContent(content) {
|
|
2
6
|
if (typeof content === 'string')
|
|
3
7
|
return content;
|
|
@@ -19,6 +23,11 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
19
23
|
const content = Array.isArray(payload?.content) ? payload.content : [];
|
|
20
24
|
const textParts = [];
|
|
21
25
|
const toolCalls = [];
|
|
26
|
+
const inferredToolCalls = [];
|
|
27
|
+
const reasoningParts = [];
|
|
28
|
+
if (typeof payload?.reasoning_content === 'string' && payload.reasoning_content.trim().length) {
|
|
29
|
+
reasoningParts.push(String(payload.reasoning_content).trim());
|
|
30
|
+
}
|
|
22
31
|
for (const part of content) {
|
|
23
32
|
if (!part || typeof part !== 'object')
|
|
24
33
|
continue;
|
|
@@ -50,6 +59,21 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
50
59
|
toolCalls.push({ id, name, args });
|
|
51
60
|
}
|
|
52
61
|
}
|
|
62
|
+
else if (kind === 'thinking' || kind === 'reasoning') {
|
|
63
|
+
const text = typeof part.text === 'string'
|
|
64
|
+
? part.text
|
|
65
|
+
: flattenAnthropicContent(part);
|
|
66
|
+
if (text) {
|
|
67
|
+
const { cleanedText, toolCalls: inferred } = extractToolCallsFromReasoningText(text, { idPrefix: 'anthropic_reasoning' });
|
|
68
|
+
const trimmed = cleanedText.trim();
|
|
69
|
+
if (trimmed.length) {
|
|
70
|
+
reasoningParts.push(trimmed);
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(inferred) && inferred.length) {
|
|
73
|
+
inferredToolCalls.push(...inferred);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
53
77
|
}
|
|
54
78
|
const mapFinishReason = (reason) => {
|
|
55
79
|
switch (reason) {
|
|
@@ -59,6 +83,60 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
59
83
|
default: return 'stop';
|
|
60
84
|
}
|
|
61
85
|
};
|
|
86
|
+
const canonicalToolCalls = toolCalls.map((tc) => ({
|
|
87
|
+
id: tc.id,
|
|
88
|
+
type: 'function',
|
|
89
|
+
function: { name: tc.name, arguments: tc.args }
|
|
90
|
+
}));
|
|
91
|
+
if (inferredToolCalls.length) {
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
for (const existing of canonicalToolCalls) {
|
|
94
|
+
const key = deriveToolCallKey(existing);
|
|
95
|
+
if (key)
|
|
96
|
+
seen.add(key);
|
|
97
|
+
}
|
|
98
|
+
for (const inferred of inferredToolCalls) {
|
|
99
|
+
const key = deriveToolCallKey(inferred);
|
|
100
|
+
if (key && seen.has(key))
|
|
101
|
+
continue;
|
|
102
|
+
canonicalToolCalls.push(inferred);
|
|
103
|
+
if (key)
|
|
104
|
+
seen.add(key);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const message = {
|
|
108
|
+
role: typeof payload.role === 'string' ? payload.role : 'assistant',
|
|
109
|
+
content: textParts.join('\n')
|
|
110
|
+
};
|
|
111
|
+
if (canonicalToolCalls.length) {
|
|
112
|
+
message.tool_calls = canonicalToolCalls;
|
|
113
|
+
}
|
|
114
|
+
if (reasoningParts.length) {
|
|
115
|
+
message.reasoning_content = reasoningParts.join('\n');
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
119
|
+
const actions = resolvePolicyActions(bridgePolicy, 'response_inbound');
|
|
120
|
+
if (actions?.length) {
|
|
121
|
+
const actionState = createBridgeActionState({
|
|
122
|
+
messages: [message],
|
|
123
|
+
rawResponse: payload
|
|
124
|
+
});
|
|
125
|
+
runBridgeActionPipeline({
|
|
126
|
+
stage: 'response_inbound',
|
|
127
|
+
actions,
|
|
128
|
+
protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
|
|
129
|
+
moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
|
|
130
|
+
requestId: typeof payload.id === 'string' ? payload.id : undefined,
|
|
131
|
+
state: actionState
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// ignore policy failures
|
|
137
|
+
}
|
|
138
|
+
const stopReason = typeof payload['stop_reason'] === 'string' ? payload['stop_reason'] : undefined;
|
|
139
|
+
const finishReason = canonicalToolCalls.length ? 'tool_calls' : mapFinishReason(stopReason);
|
|
62
140
|
return {
|
|
63
141
|
id: typeof payload.id === 'string' ? payload.id : `chatcmpl_${Date.now()}`,
|
|
64
142
|
object: 'chat.completion',
|
|
@@ -67,16 +145,8 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
67
145
|
choices: [
|
|
68
146
|
{
|
|
69
147
|
index: 0,
|
|
70
|
-
finish_reason:
|
|
71
|
-
message
|
|
72
|
-
role: typeof payload.role === 'string' ? payload.role : 'assistant',
|
|
73
|
-
content: textParts.join('\n'),
|
|
74
|
-
tool_calls: toolCalls.map((tc) => ({
|
|
75
|
-
id: tc.id,
|
|
76
|
-
type: 'function',
|
|
77
|
-
function: { name: tc.name, arguments: tc.args }
|
|
78
|
-
}))
|
|
79
|
-
}
|
|
148
|
+
finish_reason: finishReason,
|
|
149
|
+
message
|
|
80
150
|
}
|
|
81
151
|
],
|
|
82
152
|
usage: payload['usage'] && typeof payload['usage'] === 'object'
|
|
@@ -87,9 +157,38 @@ export function buildOpenAIChatFromAnthropicMessage(payload) {
|
|
|
87
157
|
export function buildAnthropicResponseFromChat(chatResponse) {
|
|
88
158
|
const choice = Array.isArray(chatResponse?.choices) ? chatResponse.choices[0] : undefined;
|
|
89
159
|
const message = choice && typeof choice === 'object' ? choice.message : undefined;
|
|
160
|
+
if (message) {
|
|
161
|
+
try {
|
|
162
|
+
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
163
|
+
const actions = resolvePolicyActions(bridgePolicy, 'response_outbound');
|
|
164
|
+
if (actions?.length) {
|
|
165
|
+
const actionState = createBridgeActionState({
|
|
166
|
+
messages: [message]
|
|
167
|
+
});
|
|
168
|
+
runBridgeActionPipeline({
|
|
169
|
+
stage: 'response_outbound',
|
|
170
|
+
actions,
|
|
171
|
+
protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
|
|
172
|
+
moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
|
|
173
|
+
requestId: typeof chatResponse.id === 'string' ? chatResponse.id : undefined,
|
|
174
|
+
state: actionState
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// ignore policy failures
|
|
180
|
+
}
|
|
181
|
+
}
|
|
90
182
|
const text = flattenAnthropicContent(message?.content);
|
|
91
183
|
const toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
|
|
92
184
|
const contentBlocks = [];
|
|
185
|
+
const reasoningRaw = message?.reasoning_content ?? message?.reasoning;
|
|
186
|
+
const reasoningText = typeof reasoningRaw === 'string'
|
|
187
|
+
? reasoningRaw.trim()
|
|
188
|
+
: flattenAnthropicContent(reasoningRaw).trim();
|
|
189
|
+
if (reasoningText) {
|
|
190
|
+
contentBlocks.push({ type: 'thinking', text: reasoningText });
|
|
191
|
+
}
|
|
93
192
|
if (text && text.trim().length) {
|
|
94
193
|
contentBlocks.push({ type: 'text', text });
|
|
95
194
|
}
|