@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.
Files changed (66) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +15 -1
  2. package/dist/conversion/compat/actions/auto-thinking.d.ts +6 -0
  3. package/dist/conversion/compat/actions/auto-thinking.js +25 -0
  4. package/dist/conversion/compat/actions/field-mapping.d.ts +14 -0
  5. package/dist/conversion/compat/actions/field-mapping.js +155 -0
  6. package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -0
  7. package/dist/conversion/compat/actions/qwen-transform.js +209 -0
  8. package/dist/conversion/compat/actions/request-rules.d.ts +24 -0
  9. package/dist/conversion/compat/actions/request-rules.js +63 -0
  10. package/dist/conversion/compat/actions/response-blacklist.d.ts +14 -0
  11. package/dist/conversion/compat/actions/response-blacklist.js +85 -0
  12. package/dist/conversion/compat/actions/response-normalize.d.ts +5 -0
  13. package/dist/conversion/compat/actions/response-normalize.js +121 -0
  14. package/dist/conversion/compat/actions/response-validate.d.ts +5 -0
  15. package/dist/conversion/compat/actions/response-validate.js +76 -0
  16. package/dist/conversion/compat/actions/snapshot.d.ts +8 -0
  17. package/dist/conversion/compat/actions/snapshot.js +21 -0
  18. package/dist/conversion/compat/actions/tool-schema.d.ts +6 -0
  19. package/dist/conversion/compat/actions/tool-schema.js +91 -0
  20. package/dist/conversion/compat/actions/universal-shape-filter.d.ts +74 -0
  21. package/dist/conversion/compat/actions/universal-shape-filter.js +382 -0
  22. package/dist/conversion/compat/profiles/chat-glm.json +187 -13
  23. package/dist/conversion/compat/profiles/chat-iflow.json +194 -26
  24. package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -35
  25. package/dist/conversion/compat/profiles/chat-qwen.json +20 -16
  26. package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  27. package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
  28. package/dist/conversion/hub/pipeline/compat/compat-engine.js +429 -5
  29. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +47 -0
  30. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
  31. package/dist/conversion/hub/pipeline/hub-pipeline.js +35 -1
  32. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  33. package/dist/conversion/hub/pipeline/target-utils.js +3 -0
  34. package/dist/conversion/hub/response/response-runtime.js +23 -15
  35. package/dist/conversion/responses/responses-host-policy.d.ts +6 -0
  36. package/dist/conversion/responses/responses-host-policy.js +14 -0
  37. package/dist/conversion/responses/responses-openai-bridge.js +51 -2
  38. package/dist/conversion/shared/anthropic-message-utils.js +6 -0
  39. package/dist/conversion/shared/bridge-actions.js +1 -1
  40. package/dist/conversion/shared/bridge-policies.js +0 -1
  41. package/dist/conversion/shared/responses-conversation-store.js +3 -26
  42. package/dist/conversion/shared/responses-reasoning-registry.d.ts +4 -0
  43. package/dist/conversion/shared/responses-reasoning-registry.js +62 -1
  44. package/dist/conversion/shared/responses-response-utils.js +23 -1
  45. package/dist/conversion/shared/tool-canonicalizer.d.ts +2 -0
  46. package/dist/conversion/shared/tool-filter-pipeline.js +11 -0
  47. package/dist/router/virtual-router/bootstrap.js +239 -39
  48. package/dist/router/virtual-router/classifier.js +19 -51
  49. package/dist/router/virtual-router/context-advisor.d.ts +21 -0
  50. package/dist/router/virtual-router/context-advisor.js +76 -0
  51. package/dist/router/virtual-router/engine.d.ts +11 -27
  52. package/dist/router/virtual-router/engine.js +191 -396
  53. package/dist/router/virtual-router/features.js +24 -607
  54. package/dist/router/virtual-router/health-manager.js +2 -7
  55. package/dist/router/virtual-router/message-utils.d.ts +7 -0
  56. package/dist/router/virtual-router/message-utils.js +66 -0
  57. package/dist/router/virtual-router/provider-registry.js +6 -2
  58. package/dist/router/virtual-router/token-estimator.d.ts +2 -0
  59. package/dist/router/virtual-router/token-estimator.js +16 -0
  60. package/dist/router/virtual-router/token-file-scanner.d.ts +15 -0
  61. package/dist/router/virtual-router/token-file-scanner.js +56 -0
  62. package/dist/router/virtual-router/tool-signals.d.ts +13 -0
  63. package/dist/router/virtual-router/tool-signals.js +403 -0
  64. package/dist/router/virtual-router/types.d.ts +21 -7
  65. package/dist/router/virtual-router/types.js +1 -0
  66. 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
- export function applyRequestCompat(profileId, payload) {
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';
@@ -64,5 +64,7 @@ export declare class HubPipeline {
64
64
  private convertSsePayload;
65
65
  private resolveSseProtocol;
66
66
  private extractModelHint;
67
+ private resolveOutboundStreamIntent;
68
+ private applyOutboundStreamPreference;
67
69
  }
68
70
  export {};
@@ -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') {
@@ -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) {