@jsonstudio/llms 0.6.147 → 0.6.198
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/gemini-openai-codec.js +15 -1
- package/dist/conversion/compat/actions/auto-thinking.d.ts +6 -0
- package/dist/conversion/compat/actions/auto-thinking.js +25 -0
- package/dist/conversion/compat/actions/field-mapping.d.ts +14 -0
- package/dist/conversion/compat/actions/field-mapping.js +155 -0
- package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -0
- package/dist/conversion/compat/actions/qwen-transform.js +209 -0
- package/dist/conversion/compat/actions/request-rules.d.ts +24 -0
- package/dist/conversion/compat/actions/request-rules.js +63 -0
- package/dist/conversion/compat/actions/response-blacklist.d.ts +14 -0
- package/dist/conversion/compat/actions/response-blacklist.js +85 -0
- package/dist/conversion/compat/actions/response-normalize.d.ts +5 -0
- package/dist/conversion/compat/actions/response-normalize.js +121 -0
- package/dist/conversion/compat/actions/response-validate.d.ts +5 -0
- package/dist/conversion/compat/actions/response-validate.js +76 -0
- package/dist/conversion/compat/actions/snapshot.d.ts +8 -0
- package/dist/conversion/compat/actions/snapshot.js +21 -0
- package/dist/conversion/compat/actions/tool-schema.d.ts +6 -0
- package/dist/conversion/compat/actions/tool-schema.js +91 -0
- package/dist/conversion/compat/actions/universal-shape-filter.d.ts +74 -0
- package/dist/conversion/compat/actions/universal-shape-filter.js +382 -0
- package/dist/conversion/compat/profiles/chat-glm.json +187 -13
- package/dist/conversion/compat/profiles/chat-iflow.json +194 -26
- package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -35
- package/dist/conversion/compat/profiles/chat-qwen.json +20 -16
- package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
- package/dist/conversion/hub/pipeline/compat/compat-engine.js +429 -5
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +47 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +35 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
- package/dist/conversion/hub/pipeline/target-utils.js +3 -0
- package/dist/conversion/hub/response/response-runtime.js +23 -15
- package/dist/conversion/responses/responses-host-policy.d.ts +6 -0
- package/dist/conversion/responses/responses-host-policy.js +14 -0
- package/dist/conversion/responses/responses-openai-bridge.js +51 -2
- package/dist/conversion/shared/anthropic-message-utils.js +6 -0
- package/dist/conversion/shared/bridge-actions.js +1 -1
- package/dist/conversion/shared/bridge-policies.js +0 -1
- package/dist/conversion/shared/responses-conversation-store.js +3 -26
- package/dist/conversion/shared/responses-reasoning-registry.d.ts +4 -0
- package/dist/conversion/shared/responses-reasoning-registry.js +62 -1
- package/dist/conversion/shared/responses-response-utils.js +23 -1
- package/dist/conversion/shared/tool-canonicalizer.d.ts +2 -0
- package/dist/conversion/shared/tool-filter-pipeline.js +11 -0
- package/dist/router/virtual-router/bootstrap.js +239 -39
- package/dist/router/virtual-router/classifier.js +19 -51
- package/dist/router/virtual-router/context-advisor.d.ts +21 -0
- package/dist/router/virtual-router/context-advisor.js +76 -0
- package/dist/router/virtual-router/engine.d.ts +11 -27
- package/dist/router/virtual-router/engine.js +191 -396
- package/dist/router/virtual-router/features.js +24 -607
- package/dist/router/virtual-router/health-manager.js +2 -7
- package/dist/router/virtual-router/message-utils.d.ts +7 -0
- package/dist/router/virtual-router/message-utils.js +66 -0
- package/dist/router/virtual-router/provider-registry.js +6 -2
- package/dist/router/virtual-router/token-estimator.d.ts +2 -0
- package/dist/router/virtual-router/token-estimator.js +16 -0
- package/dist/router/virtual-router/token-file-scanner.d.ts +15 -0
- package/dist/router/virtual-router/token-file-scanner.js +56 -0
- package/dist/router/virtual-router/tool-signals.d.ts +13 -0
- package/dist/router/virtual-router/tool-signals.js +403 -0
- package/dist/router/virtual-router/types.d.ts +21 -7
- package/dist/router/virtual-router/types.js +1 -0
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ import { resolveBridgePolicy, resolvePolicyActions } from '../shared/bridge-poli
|
|
|
3
3
|
import { normalizeChatMessageContent } from '../shared/chat-output-normalizer.js';
|
|
4
4
|
import { mapBridgeToolsToChat } from '../shared/tool-mapping.js';
|
|
5
5
|
import { prepareGeminiToolsForBridge } from '../shared/gemini-tool-utils.js';
|
|
6
|
-
import { registerResponsesReasoning, consumeResponsesReasoning, registerResponsesOutputTextMeta, consumeResponsesOutputTextMeta } from '../shared/responses-reasoning-registry.js';
|
|
6
|
+
import { registerResponsesReasoning, consumeResponsesReasoning, registerResponsesOutputTextMeta, consumeResponsesOutputTextMeta, consumeResponsesPayloadSnapshot, registerResponsesPayloadSnapshot, consumeResponsesPassthrough, registerResponsesPassthrough } from '../shared/responses-reasoning-registry.js';
|
|
7
7
|
function isObject(v) {
|
|
8
8
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
9
9
|
}
|
|
@@ -285,6 +285,20 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
285
285
|
if (preservedOutputMeta) {
|
|
286
286
|
chatResp.__responses_output_text_meta = preservedOutputMeta;
|
|
287
287
|
}
|
|
288
|
+
const payloadSnapshot = consumeResponsesPayloadSnapshot(chatResp.id);
|
|
289
|
+
if (payloadSnapshot) {
|
|
290
|
+
registerResponsesPayloadSnapshot(chatResp.id, payloadSnapshot);
|
|
291
|
+
if (typeof chatResp.request_id !== 'string') {
|
|
292
|
+
chatResp.request_id = chatResp.id;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const passthroughPayload = consumeResponsesPassthrough(chatResp.id);
|
|
296
|
+
if (passthroughPayload) {
|
|
297
|
+
registerResponsesPassthrough(chatResp.id, passthroughPayload);
|
|
298
|
+
if (typeof chatResp.request_id !== 'string') {
|
|
299
|
+
chatResp.request_id = chatResp.id;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
288
302
|
return chatResp;
|
|
289
303
|
}
|
|
290
304
|
export function buildGeminiFromOpenAIChat(chatResp) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function applyAutoThinking(payload, config) {
|
|
2
|
+
if (!config) {
|
|
3
|
+
return payload;
|
|
4
|
+
}
|
|
5
|
+
const record = payload;
|
|
6
|
+
const modelId = typeof record.model === 'string' ? record.model.toLowerCase().trim() : '';
|
|
7
|
+
if (!modelId) {
|
|
8
|
+
return payload;
|
|
9
|
+
}
|
|
10
|
+
const prefixes = config.modelPrefixes ?? [];
|
|
11
|
+
const exclude = config.excludePrefixes ?? [];
|
|
12
|
+
const matches = prefixes.length === 0
|
|
13
|
+
? true
|
|
14
|
+
: prefixes.some(prefix => modelId.startsWith(prefix.toLowerCase()));
|
|
15
|
+
const excluded = exclude.some(prefix => modelId.startsWith(prefix.toLowerCase()));
|
|
16
|
+
if (!matches || excluded) {
|
|
17
|
+
return payload;
|
|
18
|
+
}
|
|
19
|
+
const thinkingNode = record.thinking;
|
|
20
|
+
if (thinkingNode && typeof thinkingNode === 'object') {
|
|
21
|
+
return payload;
|
|
22
|
+
}
|
|
23
|
+
record.thinking = { type: 'enabled' };
|
|
24
|
+
return payload;
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type MappingType = 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
2
|
+
export interface FieldMapping {
|
|
3
|
+
sourcePath: string;
|
|
4
|
+
targetPath: string;
|
|
5
|
+
type: MappingType;
|
|
6
|
+
transform?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface FieldMappingConfig {
|
|
9
|
+
incomingMappings: FieldMapping[];
|
|
10
|
+
outgoingMappings: FieldMapping[];
|
|
11
|
+
}
|
|
12
|
+
type UnknownRecord = Record<string, unknown>;
|
|
13
|
+
export declare function applyFieldMappings(payload: UnknownRecord, mappings: FieldMapping[]): UnknownRecord;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const MODEL_PREFIX_NORMALIZATION = {
|
|
2
|
+
'gpt-': 'glm-'
|
|
3
|
+
};
|
|
4
|
+
const FINISH_REASON_MAP = {
|
|
5
|
+
tool_calls: 'tool_calls',
|
|
6
|
+
stop: 'stop',
|
|
7
|
+
length: 'length',
|
|
8
|
+
sensitive: 'content_filter',
|
|
9
|
+
network_error: 'error'
|
|
10
|
+
};
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
export function applyFieldMappings(payload, mappings) {
|
|
15
|
+
const result = { ...payload };
|
|
16
|
+
for (const mapping of mappings) {
|
|
17
|
+
applySingleMapping(result, mapping);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
function applySingleMapping(root, mapping) {
|
|
22
|
+
const sourceValue = getNestedProperty(root, mapping.sourcePath);
|
|
23
|
+
if (sourceValue === undefined) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const transformed = convertType(applyTransform(sourceValue, mapping.transform), mapping.type);
|
|
27
|
+
setNestedProperty(root, mapping.targetPath, transformed);
|
|
28
|
+
}
|
|
29
|
+
function applyTransform(value, transform) {
|
|
30
|
+
if (!transform) {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
switch (transform) {
|
|
34
|
+
case 'timestamp':
|
|
35
|
+
return typeof value === 'number' ? value : Date.now();
|
|
36
|
+
case 'lowercase':
|
|
37
|
+
return typeof value === 'string' ? value.toLowerCase() : value;
|
|
38
|
+
case 'uppercase':
|
|
39
|
+
return typeof value === 'string' ? value.toUpperCase() : value;
|
|
40
|
+
case 'normalizeModelName':
|
|
41
|
+
if (typeof value === 'string') {
|
|
42
|
+
for (const [prefix, replacement] of Object.entries(MODEL_PREFIX_NORMALIZATION)) {
|
|
43
|
+
if (value.startsWith(prefix)) {
|
|
44
|
+
return value.replace(prefix, replacement);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
case 'normalizeFinishReason':
|
|
50
|
+
if (typeof value === 'string') {
|
|
51
|
+
return FINISH_REASON_MAP[value] ?? value;
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
default:
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function convertType(value, targetType) {
|
|
59
|
+
if (value === null || value === undefined) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
switch (targetType) {
|
|
63
|
+
case 'string':
|
|
64
|
+
return String(value);
|
|
65
|
+
case 'number': {
|
|
66
|
+
const num = Number(value);
|
|
67
|
+
return Number.isNaN(num) ? 0 : num;
|
|
68
|
+
}
|
|
69
|
+
case 'boolean':
|
|
70
|
+
return Boolean(value);
|
|
71
|
+
case 'object':
|
|
72
|
+
return isRecord(value) ? value : {};
|
|
73
|
+
case 'array':
|
|
74
|
+
return Array.isArray(value) ? value : [value];
|
|
75
|
+
default:
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function getNestedProperty(obj, pathExpression) {
|
|
80
|
+
const keys = pathExpression.split('.');
|
|
81
|
+
if (pathExpression.includes('[*]')) {
|
|
82
|
+
return getWildcardProperty(obj, keys);
|
|
83
|
+
}
|
|
84
|
+
return keys.reduce((current, key) => {
|
|
85
|
+
if (!isRecord(current)) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
return current[key];
|
|
89
|
+
}, obj);
|
|
90
|
+
}
|
|
91
|
+
function getWildcardProperty(obj, keys) {
|
|
92
|
+
const results = [];
|
|
93
|
+
const processWildcard = (current, keyIndex) => {
|
|
94
|
+
if (keyIndex >= keys.length) {
|
|
95
|
+
results.push(current);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const key = keys[keyIndex];
|
|
99
|
+
if (key === '[*]') {
|
|
100
|
+
if (Array.isArray(current)) {
|
|
101
|
+
current.forEach(item => processWildcard(item, keyIndex + 1));
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (isRecord(current) && current[key] !== undefined) {
|
|
106
|
+
processWildcard(current[key], keyIndex + 1);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
processWildcard(obj, 0);
|
|
110
|
+
return results;
|
|
111
|
+
}
|
|
112
|
+
function setNestedProperty(obj, pathExpression, value) {
|
|
113
|
+
const keys = pathExpression.split('.');
|
|
114
|
+
if (pathExpression.includes('[*]')) {
|
|
115
|
+
setWildcardProperty(obj, keys, value);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const lastKey = keys.pop();
|
|
119
|
+
if (!lastKey) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const target = keys.reduce((current, key) => {
|
|
123
|
+
if (!isRecord(current[key])) {
|
|
124
|
+
current[key] = {};
|
|
125
|
+
}
|
|
126
|
+
return current[key];
|
|
127
|
+
}, obj);
|
|
128
|
+
target[lastKey] = value;
|
|
129
|
+
}
|
|
130
|
+
function setWildcardProperty(obj, keys, value) {
|
|
131
|
+
const processSetWildcard = (current, keyIndex) => {
|
|
132
|
+
if (keyIndex >= keys.length - 1) {
|
|
133
|
+
const lastKey = keys[keys.length - 1].replace('[*]', '');
|
|
134
|
+
if (Array.isArray(current)) {
|
|
135
|
+
current.forEach(item => {
|
|
136
|
+
if (isRecord(item)) {
|
|
137
|
+
item[lastKey] = value;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const key = keys[keyIndex];
|
|
144
|
+
if (key === '[*]') {
|
|
145
|
+
if (Array.isArray(current)) {
|
|
146
|
+
current.forEach(item => processSetWildcard(item, keyIndex + 1));
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (isRecord(current)) {
|
|
151
|
+
processSetWildcard(current[key], keyIndex + 1);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
processSetWildcard(obj, 0);
|
|
155
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const MODEL_MAP = {
|
|
2
|
+
'gpt-3.5-turbo': 'qwen-turbo',
|
|
3
|
+
'gpt-4': 'qwen3-coder-plus',
|
|
4
|
+
'gpt-4-turbo': 'qwen3-coder-plus',
|
|
5
|
+
'gpt-4o': 'qwen3-coder-plus'
|
|
6
|
+
};
|
|
7
|
+
const FINISH_REASON_MAP = {
|
|
8
|
+
stop: 'stop',
|
|
9
|
+
length: 'length',
|
|
10
|
+
tool_calls: 'tool_calls',
|
|
11
|
+
content_filter: 'content_filter'
|
|
12
|
+
};
|
|
13
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
14
|
+
export function applyQwenRequestTransform(payload) {
|
|
15
|
+
const cloned = structuredClone(payload);
|
|
16
|
+
const transformed = convertToQwenRequest(cloned);
|
|
17
|
+
return transformed;
|
|
18
|
+
}
|
|
19
|
+
export function applyQwenResponseTransform(payload) {
|
|
20
|
+
const cloned = structuredClone(payload);
|
|
21
|
+
const transformed = transformQwenResponseToOpenAI(cloned);
|
|
22
|
+
return transformed;
|
|
23
|
+
}
|
|
24
|
+
function convertToQwenRequest(request) {
|
|
25
|
+
const qwenRequest = {};
|
|
26
|
+
const mappedModel = mapModelName(typeof request.model === 'string' ? request.model : undefined);
|
|
27
|
+
if (mappedModel) {
|
|
28
|
+
qwenRequest.model = mappedModel;
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(request.messages)) {
|
|
31
|
+
qwenRequest.messages = structuredClone(request.messages);
|
|
32
|
+
const normalizedMessages = request.messages
|
|
33
|
+
.map(message => normalizeMessage(message))
|
|
34
|
+
.filter((entry) => entry !== null);
|
|
35
|
+
if (normalizedMessages.length > 0) {
|
|
36
|
+
qwenRequest.input = normalizedMessages;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const parameters = extractParameters(request);
|
|
40
|
+
if (Object.keys(parameters).length > 0) {
|
|
41
|
+
qwenRequest.parameters = parameters;
|
|
42
|
+
}
|
|
43
|
+
if (typeof request.stream === 'boolean') {
|
|
44
|
+
qwenRequest.stream = request.stream;
|
|
45
|
+
}
|
|
46
|
+
if (isRecord(request.response_format)) {
|
|
47
|
+
qwenRequest.response_format = structuredClone(request.response_format);
|
|
48
|
+
}
|
|
49
|
+
if (typeof request.user === 'string') {
|
|
50
|
+
qwenRequest.user = request.user;
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(request.tools)) {
|
|
53
|
+
qwenRequest.tools = sanitizeTools(request.tools);
|
|
54
|
+
}
|
|
55
|
+
if (isRecord(request.metadata)) {
|
|
56
|
+
qwenRequest.metadata = structuredClone(request.metadata);
|
|
57
|
+
}
|
|
58
|
+
return qwenRequest;
|
|
59
|
+
}
|
|
60
|
+
function sanitizeTools(tools) {
|
|
61
|
+
return tools.map(tool => {
|
|
62
|
+
if (!isRecord(tool)) {
|
|
63
|
+
return tool;
|
|
64
|
+
}
|
|
65
|
+
const normalized = {};
|
|
66
|
+
if (typeof tool.type === 'string') {
|
|
67
|
+
normalized.type = tool.type;
|
|
68
|
+
}
|
|
69
|
+
if (isRecord(tool.function)) {
|
|
70
|
+
normalized.function = structuredClone(tool.function);
|
|
71
|
+
}
|
|
72
|
+
return Object.keys(normalized).length > 0 ? normalized : tool;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function normalizeMessage(message) {
|
|
76
|
+
if (!isRecord(message)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const role = typeof message.role === 'string' ? message.role : 'user';
|
|
80
|
+
const content = normalizeMessageContent(message.content);
|
|
81
|
+
return { role, content };
|
|
82
|
+
}
|
|
83
|
+
function normalizeMessageContent(content) {
|
|
84
|
+
if (content === undefined || content === null) {
|
|
85
|
+
return [{ text: '' }];
|
|
86
|
+
}
|
|
87
|
+
if (typeof content === 'string') {
|
|
88
|
+
return [{ text: content }];
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(content)) {
|
|
91
|
+
return content.map(chunk => normalizeContentChunk(chunk));
|
|
92
|
+
}
|
|
93
|
+
if (isRecord(content) && typeof content.text === 'string') {
|
|
94
|
+
return [{ text: content.text }];
|
|
95
|
+
}
|
|
96
|
+
return [{ text: JSON.stringify(content) }];
|
|
97
|
+
}
|
|
98
|
+
function normalizeContentChunk(chunk) {
|
|
99
|
+
if (typeof chunk === 'string') {
|
|
100
|
+
return { text: chunk };
|
|
101
|
+
}
|
|
102
|
+
if (isRecord(chunk)) {
|
|
103
|
+
if (typeof chunk.text === 'string') {
|
|
104
|
+
return { text: chunk.text };
|
|
105
|
+
}
|
|
106
|
+
return structuredClone(chunk);
|
|
107
|
+
}
|
|
108
|
+
return { text: String(chunk) };
|
|
109
|
+
}
|
|
110
|
+
function extractParameters(request) {
|
|
111
|
+
const parameters = {};
|
|
112
|
+
const numericFields = [
|
|
113
|
+
{ key: 'temperature', target: 'temperature' },
|
|
114
|
+
{ key: 'top_p', target: 'top_p' },
|
|
115
|
+
{ key: 'frequency_penalty', target: 'frequency_penalty' },
|
|
116
|
+
{ key: 'presence_penalty', target: 'presence_penalty' },
|
|
117
|
+
{ key: 'max_tokens', target: 'max_output_tokens' }
|
|
118
|
+
];
|
|
119
|
+
for (const field of numericFields) {
|
|
120
|
+
const value = request[field.key];
|
|
121
|
+
if (typeof value === 'number') {
|
|
122
|
+
parameters[field.target] = value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (request.stop !== undefined) {
|
|
126
|
+
const stops = Array.isArray(request.stop) ? request.stop : [request.stop];
|
|
127
|
+
const sequences = stops.filter(item => typeof item === 'string');
|
|
128
|
+
if (sequences.length > 0) {
|
|
129
|
+
parameters.stop_sequences = sequences;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (typeof request.debug === 'boolean') {
|
|
133
|
+
parameters.debug = request.debug;
|
|
134
|
+
}
|
|
135
|
+
return parameters;
|
|
136
|
+
}
|
|
137
|
+
function transformQwenResponseToOpenAI(response) {
|
|
138
|
+
const data = isRecord(response.data) ? response.data : response;
|
|
139
|
+
const usage = isRecord(data.usage)
|
|
140
|
+
? structuredClone(data.usage)
|
|
141
|
+
: {
|
|
142
|
+
prompt_tokens: 0,
|
|
143
|
+
completion_tokens: 0,
|
|
144
|
+
total_tokens: 0
|
|
145
|
+
};
|
|
146
|
+
const transformed = {
|
|
147
|
+
id: typeof data.id === 'string' ? data.id : `chatcmpl-${Date.now()}`,
|
|
148
|
+
object: 'chat.completion',
|
|
149
|
+
created: typeof data.created === 'number' ? data.created : Math.floor(Date.now() / 1000),
|
|
150
|
+
model: typeof data.model === 'string' ? data.model : 'qwen-turbo',
|
|
151
|
+
choices: transformChoices(data.choices),
|
|
152
|
+
usage,
|
|
153
|
+
_transformed: true,
|
|
154
|
+
_originalFormat: 'qwen',
|
|
155
|
+
_targetFormat: 'openai'
|
|
156
|
+
};
|
|
157
|
+
return transformed;
|
|
158
|
+
}
|
|
159
|
+
function transformChoices(rawChoices) {
|
|
160
|
+
if (!Array.isArray(rawChoices)) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
return rawChoices.map((choice, index) => {
|
|
164
|
+
const choiceObj = isRecord(choice) ? choice : {};
|
|
165
|
+
const messageObj = isRecord(choiceObj.message) ? choiceObj.message : {};
|
|
166
|
+
return {
|
|
167
|
+
index: typeof choiceObj.index === 'number' ? choiceObj.index : index,
|
|
168
|
+
message: {
|
|
169
|
+
role: typeof messageObj.role === 'string' ? messageObj.role : 'assistant',
|
|
170
|
+
content: typeof messageObj.content === 'string' ? messageObj.content : '',
|
|
171
|
+
tool_calls: transformToolCalls(messageObj.tool_calls)
|
|
172
|
+
},
|
|
173
|
+
finish_reason: transformFinishReason(typeof choiceObj.finish_reason === 'string' ? choiceObj.finish_reason : undefined)
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function transformToolCalls(toolCalls) {
|
|
178
|
+
if (!Array.isArray(toolCalls)) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return toolCalls.map((toolCall, index) => {
|
|
182
|
+
const toolCallObj = isRecord(toolCall) ? toolCall : {};
|
|
183
|
+
const fnObj = isRecord(toolCallObj.function) ? toolCallObj.function : {};
|
|
184
|
+
const id = typeof toolCallObj.id === 'string'
|
|
185
|
+
? toolCallObj.id
|
|
186
|
+
: `call_${Date.now()}_${index}`;
|
|
187
|
+
const name = typeof fnObj.name === 'string' ? fnObj.name : '';
|
|
188
|
+
const args = typeof fnObj.arguments === 'string'
|
|
189
|
+
? fnObj.arguments
|
|
190
|
+
: JSON.stringify(fnObj.arguments ?? {});
|
|
191
|
+
return {
|
|
192
|
+
id,
|
|
193
|
+
type: 'function',
|
|
194
|
+
function: { name, arguments: args }
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function transformFinishReason(reason) {
|
|
199
|
+
if (!reason) {
|
|
200
|
+
return 'stop';
|
|
201
|
+
}
|
|
202
|
+
return FINISH_REASON_MAP[reason] ?? reason;
|
|
203
|
+
}
|
|
204
|
+
function mapModelName(model) {
|
|
205
|
+
if (!model) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
return MODEL_MAP[model] ?? model;
|
|
209
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
+
export interface RequestRulesConfig {
|
|
3
|
+
tools?: {
|
|
4
|
+
function?: {
|
|
5
|
+
removeKeys?: string[];
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
messages?: {
|
|
9
|
+
assistantToolCalls?: {
|
|
10
|
+
function?: {
|
|
11
|
+
removeKeys?: string[];
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
topLevel?: {
|
|
16
|
+
conditional?: Array<{
|
|
17
|
+
when?: {
|
|
18
|
+
tools?: 'empty' | 'present';
|
|
19
|
+
};
|
|
20
|
+
remove?: string[];
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export declare function applyRequestRules(payload: JsonObject, config?: RequestRulesConfig): JsonObject;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
export function applyRequestRules(payload, config) {
|
|
3
|
+
if (!config) {
|
|
4
|
+
return payload;
|
|
5
|
+
}
|
|
6
|
+
const cloned = { ...payload };
|
|
7
|
+
const toolRule = config.tools?.['function'];
|
|
8
|
+
if (toolRule?.removeKeys && Array.isArray(cloned.tools)) {
|
|
9
|
+
for (const toolEntry of cloned.tools) {
|
|
10
|
+
if (!isRecord(toolEntry)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const fnNode = toolEntry.function;
|
|
14
|
+
if (!isRecord(fnNode)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
for (const key of toolRule.removeKeys) {
|
|
18
|
+
delete fnNode[key];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const assistantRule = config.messages?.assistantToolCalls?.['function'];
|
|
23
|
+
if (Array.isArray(cloned.messages) && assistantRule?.removeKeys) {
|
|
24
|
+
for (const message of cloned.messages) {
|
|
25
|
+
if (!isRecord(message) || !Array.isArray(message.tool_calls)) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const callEntry of message.tool_calls) {
|
|
29
|
+
if (!isRecord(callEntry)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const fnNode = callEntry.function;
|
|
33
|
+
if (!isRecord(fnNode)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
for (const key of assistantRule.removeKeys) {
|
|
37
|
+
delete fnNode[key];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(config.topLevel?.conditional)) {
|
|
43
|
+
for (const rule of config.topLevel.conditional) {
|
|
44
|
+
if (!rule)
|
|
45
|
+
continue;
|
|
46
|
+
if (rule.when?.tools === 'empty') {
|
|
47
|
+
if (Array.isArray(cloned.tools) && cloned.tools.length === 0) {
|
|
48
|
+
for (const field of rule.remove || []) {
|
|
49
|
+
delete cloned[field];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (rule.when?.tools === 'present') {
|
|
54
|
+
if (Array.isArray(cloned.tools) && cloned.tools.length > 0) {
|
|
55
|
+
for (const field of rule.remove || []) {
|
|
56
|
+
delete cloned[field];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return cloned;
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
+
export interface ResponseBlacklistConfig {
|
|
3
|
+
paths?: string[];
|
|
4
|
+
keepCritical?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class ResponseBlacklistSanitizer {
|
|
7
|
+
private readonly cfg;
|
|
8
|
+
private readonly criticalPaths;
|
|
9
|
+
constructor(config: ResponseBlacklistConfig);
|
|
10
|
+
private isCritical;
|
|
11
|
+
private unwrapPayload;
|
|
12
|
+
private deleteByPath;
|
|
13
|
+
apply(payload: JsonObject): JsonObject;
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function isTraversable(value) {
|
|
5
|
+
return Array.isArray(value) || isRecord(value);
|
|
6
|
+
}
|
|
7
|
+
export class ResponseBlacklistSanitizer {
|
|
8
|
+
cfg;
|
|
9
|
+
criticalPaths = new Set([
|
|
10
|
+
'status',
|
|
11
|
+
'output',
|
|
12
|
+
'output_text',
|
|
13
|
+
'required_action',
|
|
14
|
+
'choices[].message.content',
|
|
15
|
+
'choices[].message.tool_calls',
|
|
16
|
+
'choices[].finish_reason'
|
|
17
|
+
]);
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.cfg = config;
|
|
20
|
+
}
|
|
21
|
+
isCritical(pathStr) {
|
|
22
|
+
if (!this.cfg.keepCritical) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return this.criticalPaths.has(pathStr);
|
|
26
|
+
}
|
|
27
|
+
unwrapPayload(payload) {
|
|
28
|
+
if (!isRecord(payload)) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
const rootCandidate = payload.data;
|
|
32
|
+
return isRecord(rootCandidate) ? rootCandidate : payload;
|
|
33
|
+
}
|
|
34
|
+
deleteByPath(obj, pathStr) {
|
|
35
|
+
const tokens = pathStr.split('.');
|
|
36
|
+
const recurse = (current, idx) => {
|
|
37
|
+
if (!isTraversable(current) || idx >= tokens.length) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const token = tokens[idx];
|
|
41
|
+
const isArrayWildcard = token.endsWith('[]');
|
|
42
|
+
const key = isArrayWildcard ? token.slice(0, -2) : token;
|
|
43
|
+
if (idx === tokens.length - 1) {
|
|
44
|
+
if (!isArrayWildcard && isRecord(current) && Object.prototype.hasOwnProperty.call(current, key)) {
|
|
45
|
+
delete current[key];
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (isArrayWildcard) {
|
|
50
|
+
if (isRecord(current)) {
|
|
51
|
+
const arr = current[key];
|
|
52
|
+
if (Array.isArray(arr)) {
|
|
53
|
+
for (const item of arr) {
|
|
54
|
+
recurse(item, idx + 1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (isRecord(current)) {
|
|
60
|
+
recurse(current[key], idx + 1);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
recurse(obj, 0);
|
|
64
|
+
}
|
|
65
|
+
apply(payload) {
|
|
66
|
+
const out = isRecord(payload) ? payload : {};
|
|
67
|
+
try {
|
|
68
|
+
const root = this.unwrapPayload(out);
|
|
69
|
+
const configPaths = Array.isArray(this.cfg?.paths) ? this.cfg.paths ?? [] : [];
|
|
70
|
+
for (const p of configPaths) {
|
|
71
|
+
if (typeof p !== 'string' || !p) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (this.isCritical(p)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
this.deleteByPath(root, p);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|