@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
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { getCompatProfile } from './compat-profile-store.js';
|
|
2
|
+
import { UniversalShapeFilter } from '../../../compat/actions/universal-shape-filter.js';
|
|
3
|
+
import { ResponseBlacklistSanitizer } from '../../../compat/actions/response-blacklist.js';
|
|
4
|
+
import { applyFieldMappings } from '../../../compat/actions/field-mapping.js';
|
|
5
|
+
import { sanitizeToolSchema } from '../../../compat/actions/tool-schema.js';
|
|
6
|
+
import { applyRequestRules } from '../../../compat/actions/request-rules.js';
|
|
7
|
+
import { applyAutoThinking as runAutoThinking } from '../../../compat/actions/auto-thinking.js';
|
|
8
|
+
import { normalizeResponsePayload } from '../../../compat/actions/response-normalize.js';
|
|
9
|
+
import { validateResponsePayload } from '../../../compat/actions/response-validate.js';
|
|
10
|
+
import { writeCompatSnapshot } from '../../../compat/actions/snapshot.js';
|
|
11
|
+
import { applyQwenRequestTransform, applyQwenResponseTransform } from '../../../compat/actions/qwen-transform.js';
|
|
2
12
|
const RATE_LIMIT_ERROR = 'ERR_COMPAT_RATE_LIMIT_DETECTED';
|
|
3
|
-
|
|
13
|
+
const INTERNAL_STATE = Symbol('compat.internal_state');
|
|
14
|
+
export function applyRequestCompat(profileId, payload, options) {
|
|
4
15
|
const profile = getCompatProfile(profileId);
|
|
5
16
|
if (!profile) {
|
|
6
17
|
return { payload };
|
|
@@ -10,9 +21,10 @@ export function applyRequestCompat(profileId, payload) {
|
|
|
10
21
|
return { payload };
|
|
11
22
|
}
|
|
12
23
|
const mutated = structuredClone(payload);
|
|
24
|
+
const state = initializeInternalState(mutated, 'request', options?.adapterContext);
|
|
13
25
|
if (Array.isArray(stage.mappings)) {
|
|
14
26
|
for (const mapping of stage.mappings) {
|
|
15
|
-
applyMapping(mutated, mapping);
|
|
27
|
+
applyMapping(mutated, mapping, state);
|
|
16
28
|
}
|
|
17
29
|
}
|
|
18
30
|
return {
|
|
@@ -20,7 +32,7 @@ export function applyRequestCompat(profileId, payload) {
|
|
|
20
32
|
appliedProfile: profile.id
|
|
21
33
|
};
|
|
22
34
|
}
|
|
23
|
-
export function applyResponseCompat(profileId, payload) {
|
|
35
|
+
export function applyResponseCompat(profileId, payload, options) {
|
|
24
36
|
const profile = getCompatProfile(profileId);
|
|
25
37
|
if (!profile) {
|
|
26
38
|
return { payload };
|
|
@@ -30,9 +42,10 @@ export function applyResponseCompat(profileId, payload) {
|
|
|
30
42
|
return { payload };
|
|
31
43
|
}
|
|
32
44
|
const mutated = structuredClone(payload);
|
|
45
|
+
const state = initializeInternalState(mutated, 'response', options?.adapterContext);
|
|
33
46
|
if (Array.isArray(stage.mappings)) {
|
|
34
47
|
for (const mapping of stage.mappings) {
|
|
35
|
-
applyMapping(mutated, mapping);
|
|
48
|
+
applyMapping(mutated, mapping, state);
|
|
36
49
|
}
|
|
37
50
|
}
|
|
38
51
|
if (Array.isArray(stage.filters)) {
|
|
@@ -40,6 +53,10 @@ export function applyResponseCompat(profileId, payload) {
|
|
|
40
53
|
applyFilter(mutated, filter);
|
|
41
54
|
}
|
|
42
55
|
}
|
|
56
|
+
const requestIdFallback = state.originalRequestId || state.adapterContext?.requestId;
|
|
57
|
+
if (requestIdFallback && typeof mutated.request_id !== 'string') {
|
|
58
|
+
mutated.request_id = requestIdFallback;
|
|
59
|
+
}
|
|
43
60
|
return {
|
|
44
61
|
payload: mutated,
|
|
45
62
|
appliedProfile: profile.id
|
|
@@ -66,7 +83,7 @@ function pickStageConfig(profile, stage) {
|
|
|
66
83
|
}
|
|
67
84
|
return null;
|
|
68
85
|
}
|
|
69
|
-
function applyMapping(root, mapping) {
|
|
86
|
+
function applyMapping(root, mapping, state) {
|
|
70
87
|
switch (mapping.action) {
|
|
71
88
|
case 'remove':
|
|
72
89
|
removePath(root, mapping.path);
|
|
@@ -95,6 +112,50 @@ function applyMapping(root, mapping) {
|
|
|
95
112
|
case 'convert_responses_output_to_choices':
|
|
96
113
|
convertResponsesOutputToChoices(root);
|
|
97
114
|
break;
|
|
115
|
+
case 'extract_glm_tool_markup':
|
|
116
|
+
extractGlmToolMarkup(root);
|
|
117
|
+
break;
|
|
118
|
+
case 'dto_unwrap':
|
|
119
|
+
dtoUnwrap(root, state);
|
|
120
|
+
break;
|
|
121
|
+
case 'dto_rewrap':
|
|
122
|
+
dtoRewrap(root, state);
|
|
123
|
+
break;
|
|
124
|
+
case 'shape_filter':
|
|
125
|
+
applyShapeFilterMapping(root, mapping, state);
|
|
126
|
+
break;
|
|
127
|
+
case 'field_map':
|
|
128
|
+
applyFieldMap(root, mapping, state);
|
|
129
|
+
break;
|
|
130
|
+
case 'tool_schema_sanitize':
|
|
131
|
+
applyToolSchemaSanitize(root, mapping);
|
|
132
|
+
break;
|
|
133
|
+
case 'apply_rules':
|
|
134
|
+
applyRules(root, mapping, state);
|
|
135
|
+
break;
|
|
136
|
+
case 'auto_thinking':
|
|
137
|
+
applyAutoThinkingAction(root, mapping, state);
|
|
138
|
+
break;
|
|
139
|
+
case 'snapshot':
|
|
140
|
+
triggerSnapshot(root, mapping, state);
|
|
141
|
+
break;
|
|
142
|
+
case 'resp_blacklist':
|
|
143
|
+
applyResponseBlacklist(root, mapping, state);
|
|
144
|
+
break;
|
|
145
|
+
case 'response_normalize':
|
|
146
|
+
applyResponseNormalize(root, mapping, state);
|
|
147
|
+
break;
|
|
148
|
+
case 'response_validate':
|
|
149
|
+
if (state.direction === 'response') {
|
|
150
|
+
validateResponsePayload(root, mapping.config);
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case 'qwen_request_transform':
|
|
154
|
+
replaceRoot(root, applyQwenRequestTransform(root));
|
|
155
|
+
break;
|
|
156
|
+
case 'qwen_response_transform':
|
|
157
|
+
replaceRoot(root, applyQwenResponseTransform(root));
|
|
158
|
+
break;
|
|
98
159
|
default:
|
|
99
160
|
break;
|
|
100
161
|
}
|
|
@@ -109,6 +170,110 @@ function applyFilter(payload, filter) {
|
|
|
109
170
|
}
|
|
110
171
|
}
|
|
111
172
|
}
|
|
173
|
+
function initializeInternalState(root, direction, adapterContext) {
|
|
174
|
+
const state = {
|
|
175
|
+
direction,
|
|
176
|
+
adapterContext,
|
|
177
|
+
originalRequestId: direction === 'response' ? extractRequestId(root) : undefined
|
|
178
|
+
};
|
|
179
|
+
Object.defineProperty(root, INTERNAL_STATE, {
|
|
180
|
+
value: state,
|
|
181
|
+
enumerable: false,
|
|
182
|
+
configurable: true
|
|
183
|
+
});
|
|
184
|
+
return state;
|
|
185
|
+
}
|
|
186
|
+
function replaceRoot(target, source) {
|
|
187
|
+
if (target === source) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
for (const key of Object.keys(target)) {
|
|
191
|
+
delete target[key];
|
|
192
|
+
}
|
|
193
|
+
for (const [key, value] of Object.entries(source)) {
|
|
194
|
+
target[key] = value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function dtoUnwrap(root, state) {
|
|
198
|
+
const original = structuredClone(root);
|
|
199
|
+
if (isRecord(original.data)) {
|
|
200
|
+
state.dtoEnvelope = { original, isDto: true };
|
|
201
|
+
replaceRoot(root, original.data);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
state.dtoEnvelope = { original, isDto: false };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function dtoRewrap(root, state) {
|
|
208
|
+
const envelope = state.dtoEnvelope;
|
|
209
|
+
if (!envelope) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!envelope.isDto) {
|
|
213
|
+
state.dtoEnvelope = undefined;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const rebuilt = structuredClone(envelope.original);
|
|
217
|
+
rebuilt.data = structuredClone(root);
|
|
218
|
+
replaceRoot(root, rebuilt);
|
|
219
|
+
state.dtoEnvelope = undefined;
|
|
220
|
+
}
|
|
221
|
+
function applyShapeFilterMapping(root, mapping, state) {
|
|
222
|
+
const target = mapping.target ?? state.direction;
|
|
223
|
+
const filter = new UniversalShapeFilter(mapping.config);
|
|
224
|
+
const filtered = target === 'request'
|
|
225
|
+
? filter.applyRequestFilter(root)
|
|
226
|
+
: filter.applyResponseFilter(root, state.adapterContext);
|
|
227
|
+
if (filtered === root) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
replaceRoot(root, filtered);
|
|
231
|
+
}
|
|
232
|
+
function applyFieldMap(root, mapping, state) {
|
|
233
|
+
const direction = mapping.direction ?? state.direction;
|
|
234
|
+
const result = applyFieldMappings(root, mapping.config);
|
|
235
|
+
replaceRoot(root, result);
|
|
236
|
+
}
|
|
237
|
+
function applyToolSchemaSanitize(root, mapping) {
|
|
238
|
+
const sanitized = sanitizeToolSchema(root, mapping.mode ?? 'glm_shell');
|
|
239
|
+
replaceRoot(root, sanitized);
|
|
240
|
+
}
|
|
241
|
+
function applyRules(root, mapping, state) {
|
|
242
|
+
if (state.direction !== 'request') {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const result = applyRequestRules(root, mapping.config);
|
|
246
|
+
replaceRoot(root, result);
|
|
247
|
+
}
|
|
248
|
+
function applyAutoThinkingAction(root, mapping, state) {
|
|
249
|
+
if (state.direction !== 'request') {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
runAutoThinking(root, mapping.config);
|
|
253
|
+
}
|
|
254
|
+
function triggerSnapshot(root, mapping, state) {
|
|
255
|
+
void writeCompatSnapshot({
|
|
256
|
+
phase: mapping.phase,
|
|
257
|
+
requestId: state.adapterContext?.requestId,
|
|
258
|
+
entryEndpoint: state.adapterContext?.entryEndpoint,
|
|
259
|
+
data: structuredClone(root)
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function applyResponseBlacklist(root, mapping, state) {
|
|
263
|
+
if (state.direction !== 'response') {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const sanitizer = new ResponseBlacklistSanitizer(mapping.config);
|
|
267
|
+
const result = sanitizer.apply(root);
|
|
268
|
+
replaceRoot(root, result);
|
|
269
|
+
}
|
|
270
|
+
function applyResponseNormalize(root, mapping, state) {
|
|
271
|
+
if (state.direction !== 'response') {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const result = normalizeResponsePayload(root, mapping.config);
|
|
275
|
+
replaceRoot(root, result);
|
|
276
|
+
}
|
|
112
277
|
function detectRateLimitText(payload, needle) {
|
|
113
278
|
if (!needle || !payload) {
|
|
114
279
|
return false;
|
|
@@ -665,3 +830,262 @@ function normalizeFinishReason(reason) {
|
|
|
665
830
|
}
|
|
666
831
|
return 'stop';
|
|
667
832
|
}
|
|
833
|
+
function extractRequestId(node) {
|
|
834
|
+
if (typeof node.request_id === 'string') {
|
|
835
|
+
return node.request_id;
|
|
836
|
+
}
|
|
837
|
+
const dataNode = isRecord(node.data)
|
|
838
|
+
? node.data
|
|
839
|
+
: undefined;
|
|
840
|
+
if (dataNode && typeof dataNode.request_id === 'string') {
|
|
841
|
+
return dataNode.request_id;
|
|
842
|
+
}
|
|
843
|
+
return undefined;
|
|
844
|
+
}
|
|
845
|
+
function extractGlmToolMarkup(root) {
|
|
846
|
+
const choicesRaw = root?.choices;
|
|
847
|
+
const choices = Array.isArray(choicesRaw) ? choicesRaw : [];
|
|
848
|
+
choices.forEach((choice, index) => {
|
|
849
|
+
if (!choice || typeof choice !== 'object') {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const message = choice.message;
|
|
853
|
+
if (!message || typeof message !== 'object') {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const msgRecord = message;
|
|
857
|
+
const contentField = msgRecord.content;
|
|
858
|
+
const reasoningField = msgRecord.reasoning_content;
|
|
859
|
+
const text = contentField !== undefined
|
|
860
|
+
? flattenContent(contentField)
|
|
861
|
+
: (typeof reasoningField === 'string' ? reasoningField : '');
|
|
862
|
+
if (!text) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const extraction = extractToolCallsFromText(text, index + 1);
|
|
866
|
+
if (!extraction) {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
if (extraction.toolCalls.length) {
|
|
870
|
+
msgRecord.tool_calls = extraction.toolCalls;
|
|
871
|
+
if (contentField !== undefined) {
|
|
872
|
+
msgRecord.content = null;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (extraction.reasoningText) {
|
|
876
|
+
msgRecord.reasoning_content = extraction.reasoningText;
|
|
877
|
+
}
|
|
878
|
+
else if ('reasoning_content' in msgRecord) {
|
|
879
|
+
delete msgRecord.reasoning_content;
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
function flattenContent(content, depth = 0) {
|
|
884
|
+
if (depth > 4 || content == null) {
|
|
885
|
+
return '';
|
|
886
|
+
}
|
|
887
|
+
if (typeof content === 'string') {
|
|
888
|
+
return content;
|
|
889
|
+
}
|
|
890
|
+
if (Array.isArray(content)) {
|
|
891
|
+
return content.map((entry) => flattenContent(entry, depth + 1)).join('');
|
|
892
|
+
}
|
|
893
|
+
if (typeof content === 'object') {
|
|
894
|
+
const record = content;
|
|
895
|
+
if (typeof record.text === 'string') {
|
|
896
|
+
return record.text;
|
|
897
|
+
}
|
|
898
|
+
if (record.content !== undefined) {
|
|
899
|
+
return flattenContent(record.content, depth + 1);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return '';
|
|
903
|
+
}
|
|
904
|
+
const GLM_CUSTOM_TAG = /<tool_call(?:\s+name="([^"]+)")?>([\s\S]*?)<\/tool_call>/gi;
|
|
905
|
+
const GLM_TAGGED_SEQUENCE = /<tool_call(?:\s+name="([^"]+)")?\s*>([\s\S]*?)(?:<\/tool_call>|(?=<tool_call)|$)/gi;
|
|
906
|
+
const GLM_TAGGED_BLOCK = /<arg_key>([\s\S]*?)<\/arg_key>\s*<arg_value>([\s\S]*?)<\/arg_value>/gi;
|
|
907
|
+
const GLM_INLINE_NAME = /^[\s\r\n]*([A-Za-z0-9_.:-]+)/;
|
|
908
|
+
const GENERIC_PATTERNS = [
|
|
909
|
+
[/```(?:tool|function|tool_call|function_call)?\s*([\s\S]*?)\s*```/gi, (match) => ({ body: match[1] ?? '' })],
|
|
910
|
+
[/\[(tool_call|function_call)(?:\s+name="([^"]+)")?\]([\s\S]*?)\[\/\1\]/gi, (match) => ({ body: match[3] ?? '', nameHint: match[2] })],
|
|
911
|
+
[/(tool_call|function_call)\s*[:=]\s*({[\s\S]+?})/gi, (match) => ({ body: match[2] ?? '' })]
|
|
912
|
+
];
|
|
913
|
+
function extractToolCallsFromText(text, choiceIndex) {
|
|
914
|
+
const matches = [];
|
|
915
|
+
const applyPattern = (pattern, factory) => {
|
|
916
|
+
pattern.lastIndex = 0;
|
|
917
|
+
let exec;
|
|
918
|
+
while ((exec = pattern.exec(text))) {
|
|
919
|
+
const payload = factory(exec);
|
|
920
|
+
if (!payload)
|
|
921
|
+
continue;
|
|
922
|
+
const parsed = parseToolCall(payload.body, payload.nameHint);
|
|
923
|
+
if (!parsed)
|
|
924
|
+
continue;
|
|
925
|
+
matches.push({
|
|
926
|
+
start: exec.index,
|
|
927
|
+
end: exec.index + exec[0].length,
|
|
928
|
+
call: parsed
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
applyPattern(GLM_CUSTOM_TAG, (match) => ({ body: match[2] ?? '', nameHint: match[1] }));
|
|
933
|
+
for (const [pattern, factory] of GENERIC_PATTERNS) {
|
|
934
|
+
applyPattern(pattern, factory);
|
|
935
|
+
}
|
|
936
|
+
applyTaggedArgPatterns(text, matches);
|
|
937
|
+
if (!matches.length && typeof text === 'string' && text.includes('<arg_key>')) {
|
|
938
|
+
GLM_INLINE_NAME.lastIndex = 0;
|
|
939
|
+
const inline = GLM_INLINE_NAME.exec(text);
|
|
940
|
+
GLM_INLINE_NAME.lastIndex = 0;
|
|
941
|
+
if (inline && inline[1]) {
|
|
942
|
+
const name = inline[1].trim();
|
|
943
|
+
const block = text.slice(inline[0].length);
|
|
944
|
+
const argsRecord = parseTaggedArgBlock(block);
|
|
945
|
+
if (name && argsRecord) {
|
|
946
|
+
matches.push({
|
|
947
|
+
start: 0,
|
|
948
|
+
end: text.length,
|
|
949
|
+
call: {
|
|
950
|
+
name,
|
|
951
|
+
args: safeStringify(argsRecord)
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
matches.sort((a, b) => a.start - b.start);
|
|
958
|
+
const toolCalls = matches.map((entry, idx) => ({
|
|
959
|
+
id: `glm_tool_${choiceIndex}_${idx + 1}`,
|
|
960
|
+
type: 'function',
|
|
961
|
+
function: {
|
|
962
|
+
name: entry.call.name,
|
|
963
|
+
arguments: entry.call.args
|
|
964
|
+
}
|
|
965
|
+
}));
|
|
966
|
+
matches.sort((a, b) => b.start - a.start);
|
|
967
|
+
let cleaned = text;
|
|
968
|
+
for (const entry of matches) {
|
|
969
|
+
cleaned = cleaned.slice(0, entry.start) + cleaned.slice(entry.end);
|
|
970
|
+
}
|
|
971
|
+
const reasoningText = cleaned.trim();
|
|
972
|
+
return {
|
|
973
|
+
toolCalls,
|
|
974
|
+
reasoningText: reasoningText.length ? reasoningText : undefined
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
function parseToolCall(body, nameHint) {
|
|
978
|
+
if (!body || typeof body !== 'string') {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
const trimmed = body.trim();
|
|
982
|
+
if (!trimmed.length) {
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
try {
|
|
986
|
+
const parsed = JSON.parse(trimmed);
|
|
987
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
const record = parsed;
|
|
991
|
+
const candidateName = (typeof record.name === 'string' && record.name.trim().length ? record.name.trim() : undefined) ??
|
|
992
|
+
(typeof record.tool_name === 'string' && record.tool_name.trim().length ? record.tool_name.trim() : undefined) ??
|
|
993
|
+
(typeof record.tool === 'string' && record.tool.trim().length ? record.tool.trim() : undefined) ??
|
|
994
|
+
(nameHint && nameHint.trim().length ? nameHint.trim() : undefined);
|
|
995
|
+
if (!candidateName) {
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
const argsSource = record.arguments ??
|
|
999
|
+
record.input ??
|
|
1000
|
+
record.params ??
|
|
1001
|
+
record.parameters ??
|
|
1002
|
+
record.payload ??
|
|
1003
|
+
{};
|
|
1004
|
+
let args = '{}';
|
|
1005
|
+
if (typeof argsSource === 'string' && argsSource.trim().length) {
|
|
1006
|
+
args = argsSource.trim();
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
try {
|
|
1010
|
+
args = JSON.stringify(argsSource ?? {});
|
|
1011
|
+
}
|
|
1012
|
+
catch {
|
|
1013
|
+
args = '{}';
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return { name: candidateName, args };
|
|
1017
|
+
}
|
|
1018
|
+
catch {
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function applyTaggedArgPatterns(text, matches) {
|
|
1023
|
+
if (!text || typeof text !== 'string') {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
GLM_TAGGED_SEQUENCE.lastIndex = 0;
|
|
1027
|
+
let exec;
|
|
1028
|
+
while ((exec = GLM_TAGGED_SEQUENCE.exec(text))) {
|
|
1029
|
+
let name = typeof exec[1] === 'string' ? exec[1].trim() : '';
|
|
1030
|
+
let block = exec[2] ?? '';
|
|
1031
|
+
if (!name) {
|
|
1032
|
+
const inline = GLM_INLINE_NAME.exec(block);
|
|
1033
|
+
if (inline && inline[1]) {
|
|
1034
|
+
name = inline[1].trim();
|
|
1035
|
+
block = block.slice(inline[0].length);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (!name) {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
const argsRecord = parseTaggedArgBlock(block);
|
|
1042
|
+
if (!argsRecord) {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
matches.push({
|
|
1046
|
+
start: exec.index,
|
|
1047
|
+
end: exec.index + exec[0].length,
|
|
1048
|
+
call: {
|
|
1049
|
+
name,
|
|
1050
|
+
args: safeStringify(argsRecord)
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function parseTaggedArgBlock(block) {
|
|
1056
|
+
if (!block || typeof block !== 'string') {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
const record = {};
|
|
1060
|
+
GLM_TAGGED_BLOCK.lastIndex = 0;
|
|
1061
|
+
let exec;
|
|
1062
|
+
while ((exec = GLM_TAGGED_BLOCK.exec(block))) {
|
|
1063
|
+
const key = typeof exec[1] === 'string' ? exec[1].trim() : '';
|
|
1064
|
+
if (!key) {
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
const rawValue = typeof exec[2] === 'string' ? exec[2].trim() : '';
|
|
1068
|
+
record[key] = coerceTaggedValue(rawValue);
|
|
1069
|
+
}
|
|
1070
|
+
return Object.keys(record).length ? record : null;
|
|
1071
|
+
}
|
|
1072
|
+
function coerceTaggedValue(raw) {
|
|
1073
|
+
if (!raw) {
|
|
1074
|
+
return '';
|
|
1075
|
+
}
|
|
1076
|
+
const trimmed = raw.trim();
|
|
1077
|
+
try {
|
|
1078
|
+
return JSON.parse(trimmed);
|
|
1079
|
+
}
|
|
1080
|
+
catch {
|
|
1081
|
+
return trimmed;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function safeStringify(value) {
|
|
1085
|
+
try {
|
|
1086
|
+
return JSON.stringify(value ?? {});
|
|
1087
|
+
}
|
|
1088
|
+
catch {
|
|
1089
|
+
return '{}';
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { JsonObject, JsonValue } from '../../types/json.js';
|
|
2
|
+
import type { FilterConfig as ShapeFilterConfig } from '../../../compat/actions/universal-shape-filter.js';
|
|
3
|
+
import type { ResponseBlacklistConfig } from '../../../compat/actions/response-blacklist.js';
|
|
4
|
+
import type { FieldMapping } from '../../../compat/actions/field-mapping.js';
|
|
5
|
+
import type { RequestRulesConfig } from '../../../compat/actions/request-rules.js';
|
|
6
|
+
import type { AutoThinkingConfig } from '../../../compat/actions/auto-thinking.js';
|
|
7
|
+
import type { ResponseNormalizeConfig } from '../../../compat/actions/response-normalize.js';
|
|
8
|
+
import type { ResponseValidateConfig } from '../../../compat/actions/response-validate.js';
|
|
2
9
|
export type CompatDirection = 'request' | 'response';
|
|
3
10
|
export interface CompatProfileConfig {
|
|
4
11
|
id: string;
|
|
@@ -51,6 +58,46 @@ export type MappingInstruction = {
|
|
|
51
58
|
fallback?: JsonValue;
|
|
52
59
|
} | {
|
|
53
60
|
action: 'convert_responses_output_to_choices';
|
|
61
|
+
} | {
|
|
62
|
+
action: 'extract_glm_tool_markup';
|
|
63
|
+
} | {
|
|
64
|
+
action: 'dto_unwrap';
|
|
65
|
+
} | {
|
|
66
|
+
action: 'dto_rewrap';
|
|
67
|
+
} | {
|
|
68
|
+
action: 'shape_filter';
|
|
69
|
+
config: ShapeFilterConfig;
|
|
70
|
+
target?: CompatDirection;
|
|
71
|
+
} | {
|
|
72
|
+
action: 'field_map';
|
|
73
|
+
direction?: 'incoming' | 'outgoing';
|
|
74
|
+
config: FieldMapping[];
|
|
75
|
+
} | {
|
|
76
|
+
action: 'tool_schema_sanitize';
|
|
77
|
+
mode?: 'glm_shell';
|
|
78
|
+
} | {
|
|
79
|
+
action: 'apply_rules';
|
|
80
|
+
config: RequestRulesConfig;
|
|
81
|
+
} | {
|
|
82
|
+
action: 'auto_thinking';
|
|
83
|
+
config: AutoThinkingConfig;
|
|
84
|
+
} | {
|
|
85
|
+
action: 'snapshot';
|
|
86
|
+
phase: 'compat-pre' | 'compat-post';
|
|
87
|
+
channel?: string;
|
|
88
|
+
} | {
|
|
89
|
+
action: 'resp_blacklist';
|
|
90
|
+
config: ResponseBlacklistConfig;
|
|
91
|
+
} | {
|
|
92
|
+
action: 'response_normalize';
|
|
93
|
+
config?: ResponseNormalizeConfig;
|
|
94
|
+
} | {
|
|
95
|
+
action: 'response_validate';
|
|
96
|
+
config?: ResponseValidateConfig;
|
|
97
|
+
} | {
|
|
98
|
+
action: 'qwen_request_transform';
|
|
99
|
+
} | {
|
|
100
|
+
action: 'qwen_response_transform';
|
|
54
101
|
};
|
|
55
102
|
export type FilterInstruction = {
|
|
56
103
|
action: 'rate_limit_text';
|
|
@@ -142,6 +142,8 @@ export class HubPipeline {
|
|
|
142
142
|
catch {
|
|
143
143
|
// logging must not break routing
|
|
144
144
|
}
|
|
145
|
+
const outboundStream = this.resolveOutboundStreamIntent(routing.target?.streaming);
|
|
146
|
+
this.applyOutboundStreamPreference(workingRequest, outboundStream);
|
|
145
147
|
const outboundAdapterContext = this.buildAdapterContext(normalized, routing.target);
|
|
146
148
|
if (routing.target?.compatibilityProfile) {
|
|
147
149
|
outboundAdapterContext.compatibilityProfile = routing.target.compatibilityProfile;
|
|
@@ -226,7 +228,8 @@ export class HubPipeline {
|
|
|
226
228
|
stream: normalized.stream,
|
|
227
229
|
processMode: normalized.processMode,
|
|
228
230
|
routeHint: normalized.routeHint,
|
|
229
|
-
target: routing.target
|
|
231
|
+
target: routing.target,
|
|
232
|
+
...(typeof outboundStream === 'boolean' ? { providerStream: outboundStream } : {})
|
|
230
233
|
};
|
|
231
234
|
return {
|
|
232
235
|
requestId: normalized.id,
|
|
@@ -508,6 +511,37 @@ export class HubPipeline {
|
|
|
508
511
|
}
|
|
509
512
|
return undefined;
|
|
510
513
|
}
|
|
514
|
+
resolveOutboundStreamIntent(providerPreference) {
|
|
515
|
+
if (providerPreference === 'always') {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
if (providerPreference === 'never') {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
applyOutboundStreamPreference(request, stream) {
|
|
524
|
+
if (!request || typeof request !== 'object') {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const parameters = request.parameters || {};
|
|
528
|
+
const nextParameters = { ...parameters };
|
|
529
|
+
if (typeof stream === 'boolean') {
|
|
530
|
+
nextParameters.stream = stream;
|
|
531
|
+
}
|
|
532
|
+
else if ('stream' in nextParameters) {
|
|
533
|
+
delete nextParameters.stream;
|
|
534
|
+
}
|
|
535
|
+
request.parameters = nextParameters;
|
|
536
|
+
if (request.metadata && typeof request.metadata === 'object') {
|
|
537
|
+
if (typeof stream === 'boolean') {
|
|
538
|
+
request.metadata.outboundStream = stream;
|
|
539
|
+
}
|
|
540
|
+
else if ('outboundStream' in request.metadata) {
|
|
541
|
+
delete request.metadata.outboundStream;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
511
545
|
}
|
|
512
546
|
function normalizeToolCallIdStyleCandidate(value) {
|
|
513
547
|
if (typeof value !== 'string') {
|
package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js
CHANGED
|
@@ -5,7 +5,7 @@ function pickCompatProfile(adapterContext) {
|
|
|
5
5
|
}
|
|
6
6
|
export async function runReqOutboundStage3Compat(options) {
|
|
7
7
|
const profile = pickCompatProfile(options.adapterContext);
|
|
8
|
-
const result = applyRequestCompat(profile, options.payload);
|
|
8
|
+
const result = applyRequestCompat(profile, options.payload, { adapterContext: options.adapterContext });
|
|
9
9
|
options.stageRecorder?.record('req_outbound_stage3_compat', {
|
|
10
10
|
applied: Boolean(result.appliedProfile),
|
|
11
11
|
profile: result.appliedProfile || profile || 'passthrough'
|
|
@@ -14,7 +14,7 @@ export async function runReqOutboundStage3Compat(options) {
|
|
|
14
14
|
}
|
|
15
15
|
export function runRespInboundStageCompatResponse(options) {
|
|
16
16
|
const profile = pickCompatProfile(options.adapterContext);
|
|
17
|
-
const result = applyResponseCompat(profile, options.payload);
|
|
17
|
+
const result = applyResponseCompat(profile, options.payload, { adapterContext: options.adapterContext });
|
|
18
18
|
options.stageRecorder?.record('resp_inbound_stage_compat', {
|
|
19
19
|
applied: Boolean(result.appliedProfile),
|
|
20
20
|
profile: result.appliedProfile || profile || 'passthrough'
|
|
@@ -12,6 +12,9 @@ export function applyTargetMetadata(metadata, target, routeName, originalModel)
|
|
|
12
12
|
if (target.responsesConfig?.toolCallIdStyle) {
|
|
13
13
|
metadata.toolCallIdStyle = target.responsesConfig.toolCallIdStyle;
|
|
14
14
|
}
|
|
15
|
+
if (target.streaming) {
|
|
16
|
+
metadata.targetStreaming = target.streaming;
|
|
17
|
+
}
|
|
15
18
|
if (originalModel && typeof originalModel === 'string' && originalModel.trim()) {
|
|
16
19
|
const trimmed = originalModel.trim();
|
|
17
20
|
if (typeof metadata.originalModelId !== 'string' || !metadata.originalModelId) {
|