@jsonstudio/llms 0.6.147 → 0.6.187

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 (63) 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 +177 -9
  24. package/dist/conversion/compat/profiles/chat-lmstudio.json +10 -2
  25. package/dist/conversion/compat/profiles/chat-qwen.json +14 -10
  26. package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
  27. package/dist/conversion/hub/pipeline/compat/compat-engine.js +409 -5
  28. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +47 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
  30. package/dist/conversion/hub/pipeline/hub-pipeline.js +35 -1
  31. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  32. package/dist/conversion/hub/pipeline/target-utils.js +3 -0
  33. package/dist/conversion/hub/response/response-runtime.js +23 -15
  34. package/dist/conversion/responses/responses-host-policy.d.ts +6 -0
  35. package/dist/conversion/responses/responses-host-policy.js +14 -0
  36. package/dist/conversion/responses/responses-openai-bridge.js +51 -2
  37. package/dist/conversion/shared/anthropic-message-utils.js +6 -0
  38. package/dist/conversion/shared/bridge-actions.js +1 -1
  39. package/dist/conversion/shared/bridge-policies.js +0 -1
  40. package/dist/conversion/shared/responses-conversation-store.js +3 -26
  41. package/dist/conversion/shared/responses-reasoning-registry.d.ts +4 -0
  42. package/dist/conversion/shared/responses-reasoning-registry.js +62 -1
  43. package/dist/conversion/shared/responses-response-utils.js +23 -1
  44. package/dist/conversion/shared/tool-canonicalizer.d.ts +2 -0
  45. package/dist/conversion/shared/tool-filter-pipeline.js +11 -0
  46. package/dist/router/virtual-router/bootstrap.js +218 -39
  47. package/dist/router/virtual-router/classifier.js +19 -51
  48. package/dist/router/virtual-router/context-advisor.d.ts +21 -0
  49. package/dist/router/virtual-router/context-advisor.js +76 -0
  50. package/dist/router/virtual-router/engine.d.ts +11 -27
  51. package/dist/router/virtual-router/engine.js +191 -396
  52. package/dist/router/virtual-router/features.js +24 -607
  53. package/dist/router/virtual-router/health-manager.js +2 -7
  54. package/dist/router/virtual-router/message-utils.d.ts +7 -0
  55. package/dist/router/virtual-router/message-utils.js +66 -0
  56. package/dist/router/virtual-router/provider-registry.js +6 -2
  57. package/dist/router/virtual-router/token-estimator.d.ts +2 -0
  58. package/dist/router/virtual-router/token-estimator.js +16 -0
  59. package/dist/router/virtual-router/tool-signals.d.ts +13 -0
  60. package/dist/router/virtual-router/tool-signals.js +403 -0
  61. package/dist/router/virtual-router/types.d.ts +21 -7
  62. package/dist/router/virtual-router/types.js +1 -0
  63. 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,242 @@ 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
+ matches.sort((a, b) => a.start - b.start);
938
+ const toolCalls = matches.map((entry, idx) => ({
939
+ id: `glm_tool_${choiceIndex}_${idx + 1}`,
940
+ type: 'function',
941
+ function: {
942
+ name: entry.call.name,
943
+ arguments: entry.call.args
944
+ }
945
+ }));
946
+ matches.sort((a, b) => b.start - a.start);
947
+ let cleaned = text;
948
+ for (const entry of matches) {
949
+ cleaned = cleaned.slice(0, entry.start) + cleaned.slice(entry.end);
950
+ }
951
+ const reasoningText = cleaned.trim();
952
+ return {
953
+ toolCalls,
954
+ reasoningText: reasoningText.length ? reasoningText : undefined
955
+ };
956
+ }
957
+ function parseToolCall(body, nameHint) {
958
+ if (!body || typeof body !== 'string') {
959
+ return null;
960
+ }
961
+ const trimmed = body.trim();
962
+ if (!trimmed.length) {
963
+ return null;
964
+ }
965
+ try {
966
+ const parsed = JSON.parse(trimmed);
967
+ if (!parsed || typeof parsed !== 'object') {
968
+ return null;
969
+ }
970
+ const record = parsed;
971
+ const candidateName = (typeof record.name === 'string' && record.name.trim().length ? record.name.trim() : undefined) ??
972
+ (typeof record.tool_name === 'string' && record.tool_name.trim().length ? record.tool_name.trim() : undefined) ??
973
+ (typeof record.tool === 'string' && record.tool.trim().length ? record.tool.trim() : undefined) ??
974
+ (nameHint && nameHint.trim().length ? nameHint.trim() : undefined);
975
+ if (!candidateName) {
976
+ return null;
977
+ }
978
+ const argsSource = record.arguments ??
979
+ record.input ??
980
+ record.params ??
981
+ record.parameters ??
982
+ record.payload ??
983
+ {};
984
+ let args = '{}';
985
+ if (typeof argsSource === 'string' && argsSource.trim().length) {
986
+ args = argsSource.trim();
987
+ }
988
+ else {
989
+ try {
990
+ args = JSON.stringify(argsSource ?? {});
991
+ }
992
+ catch {
993
+ args = '{}';
994
+ }
995
+ }
996
+ return { name: candidateName, args };
997
+ }
998
+ catch {
999
+ return null;
1000
+ }
1001
+ }
1002
+ function applyTaggedArgPatterns(text, matches) {
1003
+ if (!text || typeof text !== 'string') {
1004
+ return;
1005
+ }
1006
+ GLM_TAGGED_SEQUENCE.lastIndex = 0;
1007
+ let exec;
1008
+ while ((exec = GLM_TAGGED_SEQUENCE.exec(text))) {
1009
+ let name = typeof exec[1] === 'string' ? exec[1].trim() : '';
1010
+ let block = exec[2] ?? '';
1011
+ if (!name) {
1012
+ const inline = GLM_INLINE_NAME.exec(block);
1013
+ if (inline && inline[1]) {
1014
+ name = inline[1].trim();
1015
+ block = block.slice(inline[0].length);
1016
+ }
1017
+ }
1018
+ if (!name) {
1019
+ continue;
1020
+ }
1021
+ const argsRecord = parseTaggedArgBlock(block);
1022
+ if (!argsRecord) {
1023
+ continue;
1024
+ }
1025
+ matches.push({
1026
+ start: exec.index,
1027
+ end: exec.index + exec[0].length,
1028
+ call: {
1029
+ name,
1030
+ args: safeStringify(argsRecord)
1031
+ }
1032
+ });
1033
+ }
1034
+ }
1035
+ function parseTaggedArgBlock(block) {
1036
+ if (!block || typeof block !== 'string') {
1037
+ return null;
1038
+ }
1039
+ const record = {};
1040
+ GLM_TAGGED_BLOCK.lastIndex = 0;
1041
+ let exec;
1042
+ while ((exec = GLM_TAGGED_BLOCK.exec(block))) {
1043
+ const key = typeof exec[1] === 'string' ? exec[1].trim() : '';
1044
+ if (!key) {
1045
+ continue;
1046
+ }
1047
+ const rawValue = typeof exec[2] === 'string' ? exec[2].trim() : '';
1048
+ record[key] = coerceTaggedValue(rawValue);
1049
+ }
1050
+ return Object.keys(record).length ? record : null;
1051
+ }
1052
+ function coerceTaggedValue(raw) {
1053
+ if (!raw) {
1054
+ return '';
1055
+ }
1056
+ const trimmed = raw.trim();
1057
+ try {
1058
+ return JSON.parse(trimmed);
1059
+ }
1060
+ catch {
1061
+ return trimmed;
1062
+ }
1063
+ }
1064
+ function safeStringify(value) {
1065
+ try {
1066
+ return JSON.stringify(value ?? {});
1067
+ }
1068
+ catch {
1069
+ return '{}';
1070
+ }
1071
+ }
@@ -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) {
@@ -3,7 +3,7 @@ import { deriveToolCallKey } from '../../shared/tool-call-utils.js';
3
3
  import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
4
4
  import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
5
5
  import { normalizeAnthropicToolName } from '../../shared/anthropic-message-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 flattenAnthropicContent(content) {
8
8
  if (typeof content === 'string')
9
9
  return content;
@@ -262,6 +262,20 @@ export function buildOpenAIChatFromAnthropicMessage(payload, options) {
262
262
  if (preservedOutputMeta) {
263
263
  chatResponse.__responses_output_text_meta = preservedOutputMeta;
264
264
  }
265
+ const payloadSnapshot = consumeResponsesPayloadSnapshot(chatResponse.id);
266
+ if (payloadSnapshot) {
267
+ registerResponsesPayloadSnapshot(chatResponse.id, payloadSnapshot);
268
+ if (typeof chatResponse.request_id !== 'string') {
269
+ chatResponse.request_id = chatResponse.id;
270
+ }
271
+ }
272
+ const passthroughPayload = consumeResponsesPassthrough(chatResponse.id);
273
+ if (passthroughPayload) {
274
+ registerResponsesPassthrough(chatResponse.id, passthroughPayload);
275
+ if (typeof chatResponse.request_id !== 'string') {
276
+ chatResponse.request_id = chatResponse.id;
277
+ }
278
+ }
265
279
  if (Object.keys(aliasCollector).length && !chatResponse.anthropicToolNameMap) {
266
280
  chatResponse.anthropicToolNameMap = aliasCollector;
267
281
  }
@@ -358,27 +372,21 @@ export function buildAnthropicResponseFromChat(chatResponse, options) {
358
372
  default: return 'end_turn';
359
373
  }
360
374
  })();
361
- const promptTokens = usage && typeof usage === 'object'
362
- ? Number(usage.prompt_tokens ?? usage.input_tokens ?? 0)
363
- : 0;
364
- const completionTokens = usage && typeof usage === 'object'
365
- ? Number(usage.completion_tokens ?? usage.output_tokens ?? 0)
366
- : 0;
367
- const usagePayload = (promptTokens || completionTokens)
368
- ? {
369
- input_tokens: promptTokens,
370
- output_tokens: completionTokens
371
- }
372
- : undefined;
375
+ const canonicalId = typeof chatResponse.request_id === 'string'
376
+ ? chatResponse.request_id
377
+ : (typeof chatResponse.id === 'string' ? chatResponse.id : `resp_${Date.now()}`);
373
378
  const raw = {
374
- id: typeof chatResponse.id === 'string' ? chatResponse.id : `resp_${Date.now()}`,
379
+ id: canonicalId,
375
380
  type: 'message',
376
381
  role: 'assistant',
377
382
  content: contentBlocks,
378
383
  model: typeof chatResponse.model === 'string' ? chatResponse.model : 'unknown',
379
384
  stop_reason: stopReasonMapped,
380
385
  usage: usage && typeof usage === 'object'
381
- ? usagePayload
386
+ ? {
387
+ input_tokens: usage.prompt_tokens ?? 0,
388
+ output_tokens: usage.completion_tokens ?? 0
389
+ }
382
390
  : undefined
383
391
  };
384
392
  const sanitized = sanitizeAnthropicMessage(raw);