@jsonstudio/llms 0.6.3541 → 0.6.3685

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 (100) hide show
  1. package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +23 -114
  2. package/dist/conversion/compat/actions/auto-thinking.js +3 -2
  3. package/dist/conversion/compat/actions/deepseek-web-response.js +9 -50
  4. package/dist/conversion/compat/actions/field-mapping.js +2 -153
  5. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
  6. package/dist/conversion/compat/actions/gemini-cli-request.js +1 -1
  7. package/dist/conversion/compat/actions/glm-history-image-trim.js +3 -37
  8. package/dist/conversion/compat/actions/glm-image-content.js +3 -32
  9. package/dist/conversion/compat/actions/glm-native-compat.d.ts +6 -0
  10. package/dist/conversion/compat/actions/glm-native-compat.js +34 -0
  11. package/dist/conversion/compat/actions/glm-vision-prompt.js +3 -76
  12. package/dist/conversion/compat/actions/glm-web-search.js +10 -43
  13. package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +4 -53
  14. package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +5 -141
  15. package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +7 -28
  16. package/dist/conversion/compat/actions/iflow-native-compat.d.ts +6 -0
  17. package/dist/conversion/compat/actions/iflow-native-compat.js +36 -0
  18. package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +4 -119
  19. package/dist/conversion/compat/actions/iflow-web-search.js +14 -55
  20. package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.js +3 -104
  21. package/dist/conversion/hub/node-support.js +1 -1
  22. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +9 -1
  23. package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +5 -0
  24. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +34 -14
  25. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +14 -14
  26. package/dist/conversion/hub/pipeline/hub-pipeline.js +838 -524
  27. package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +6 -0
  28. package/dist/conversion/hub/pipeline/hub-stage-timing.js +178 -0
  29. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +6 -4
  30. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +46 -0
  31. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.d.ts +3 -0
  32. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.js +2 -1
  33. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +2 -0
  34. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +1 -0
  35. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.d.ts +3 -2
  36. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.js +18 -5
  37. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.d.ts +1 -2
  38. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.js +0 -16
  39. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +1 -1
  40. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +30 -12
  41. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +1 -0
  42. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +5 -2
  43. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +1 -1
  44. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +9 -5
  45. package/dist/conversion/hub/process/chat-process-continue-execution.js +2 -4
  46. package/dist/conversion/hub/process/chat-process-governance-orchestration.js +3 -1
  47. package/dist/conversion/hub/process/chat-process-media.d.ts +1 -0
  48. package/dist/conversion/hub/process/chat-process-media.js +36 -0
  49. package/dist/conversion/hub/process/chat-process-session-usage.d.ts +25 -0
  50. package/dist/conversion/hub/process/chat-process-session-usage.js +246 -0
  51. package/dist/conversion/hub/response/provider-response.js +13 -0
  52. package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
  53. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +0 -4
  54. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +0 -12
  55. package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -9
  56. package/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
  57. package/dist/conversion/responses/responses-openai-bridge.js +51 -24
  58. package/dist/conversion/shared/anthropic-message-utils.js +14 -1
  59. package/dist/conversion/shared/reasoning-normalizer.js +61 -0
  60. package/dist/conversion/shared/tool-governor.js +2 -4
  61. package/dist/native/router_hotpath_napi.node +0 -0
  62. package/dist/quota/quota-state.js +1 -6
  63. package/dist/router/virtual-router/bootstrap/profile-builder.js +1 -0
  64. package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +1 -0
  65. package/dist/router/virtual-router/bootstrap/provider-normalization.js +6 -0
  66. package/dist/router/virtual-router/bootstrap.js +1 -6
  67. package/dist/router/virtual-router/engine/routing-state/store.js +21 -2
  68. package/dist/router/virtual-router/engine-legacy.js +43 -0
  69. package/dist/router/virtual-router/engine-logging.d.ts +3 -0
  70. package/dist/router/virtual-router/engine-logging.js +29 -3
  71. package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.d.ts +1 -0
  72. package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.js +1 -0
  73. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +3 -0
  74. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +72 -0
  75. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +1 -1
  76. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +1 -1
  77. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.d.ts +0 -1
  78. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.js +0 -29
  79. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +1 -0
  80. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +6 -2
  81. package/dist/router/virtual-router/engine.js +28 -13
  82. package/dist/router/virtual-router/provider-registry.js +1 -0
  83. package/dist/router/virtual-router/routing-instructions/state.js +44 -2
  84. package/dist/router/virtual-router/routing-instructions/types.d.ts +6 -0
  85. package/dist/router/virtual-router/token-estimator.js +21 -0
  86. package/dist/router/virtual-router/types.d.ts +7 -0
  87. package/dist/servertool/engine.js +3 -34
  88. package/dist/servertool/handlers/followup-request-builder.js +0 -6
  89. package/dist/servertool/handlers/gemini-empty-reply-continue.js +3 -274
  90. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +0 -3
  91. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +0 -29
  92. package/dist/servertool/handlers/stop-message-auto.js +11 -9
  93. package/dist/servertool/handlers/vision.js +4 -1
  94. package/dist/servertool/server-side-tools.d.ts +0 -1
  95. package/dist/servertool/server-side-tools.js +67 -5
  96. package/dist/tools/apply-patch/execution-capturer.d.ts +1 -1
  97. package/dist/tools/apply-patch/execution-capturer.js +1 -2
  98. package/dist/tools/apply-patch/regression-capturer.js +2 -1
  99. package/dist/tools/tool-registry.js +1 -2
  100. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { runReqOutboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
2
+ import { buildGlmRequestCompatInput } from './glm-native-compat.js';
1
3
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
4
  const DEBUG_GLM_WEB_SEARCH = (process.env.ROUTECODEX_DEBUG_GLM_WEB_SEARCH || '').trim() === '1';
3
5
  export function applyGlmWebSearchRequestTransform(payload) {
@@ -6,52 +8,17 @@ export function applyGlmWebSearchRequestTransform(payload) {
6
8
  if (!isRecord(webSearchRaw)) {
7
9
  return root;
8
10
  }
9
- const webSearch = webSearchRaw;
10
- const queryValue = webSearch.query;
11
- const recencyValue = webSearch.recency;
12
- const countValue = webSearch.count;
13
- const query = typeof queryValue === 'string' ? queryValue.trim() : '';
14
- const recency = typeof recencyValue === 'string' ? recencyValue.trim() : undefined;
15
- let count;
16
- if (typeof countValue === 'number' && Number.isFinite(countValue)) {
17
- const normalized = Math.floor(countValue);
18
- if (normalized >= 1 && normalized <= 50) {
19
- count = normalized;
20
- }
21
- }
22
- if (!query) {
23
- // No meaningful search query, drop the helper object and passthrough.
24
- delete root.web_search;
25
- return root;
26
- }
27
- const webSearchConfig = {
28
- // 按 GLM 文档要求:search_engine 为必填,默认使用 search_std。
29
- search_engine: 'search_std',
30
- enable: true,
31
- search_query: query,
32
- // 返回搜索结果详情,便于我们在响应中提取摘要或调试。
33
- search_result: true
34
- };
35
- if (recency) {
36
- webSearchConfig.search_recency_filter = recency;
37
- }
38
- if (typeof count === 'number') {
39
- webSearchConfig.count = count;
40
- }
41
- // 根据 OpenAPI:tools.anyOf[*] = WebSearchToolSchema[],即一次只能选择一种工具类型。
42
- // 在 web_search 路由下,我们只保留 WebSearchToolSchema,丢弃其它 function/retrieval/MCP 工具,
43
- // 避免混用导致后端忽略 web_search 配置。
44
- const baseTool = {
45
- type: 'web_search',
46
- web_search: webSearchConfig
47
- };
48
- root.tools = [baseTool];
49
- delete root.web_search;
11
+ const query = typeof webSearchRaw.query === 'string' ? webSearchRaw.query.trim() : '';
12
+ const recency = typeof webSearchRaw.recency === 'string' ? webSearchRaw.recency.trim() : undefined;
13
+ const count = typeof webSearchRaw.count === 'number' && Number.isFinite(webSearchRaw.count)
14
+ ? Math.floor(webSearchRaw.count)
15
+ : undefined;
16
+ const normalized = runReqOutboundStage3CompatWithNative(buildGlmRequestCompatInput(payload)).payload;
50
17
  if (DEBUG_GLM_WEB_SEARCH) {
51
18
  try {
52
19
  // eslint-disable-next-line no-console
53
20
  console.log('\x1b[38;5;27m[compat][glm_web_search_request] applied web_search transform ' +
54
- `search_engine=${String(baseTool?.web_search?.search_engine ?? 'search_std')} ` +
21
+ `search_engine=search_std ` +
55
22
  `query=${JSON.stringify(query).slice(0, 200)} ` +
56
23
  `count=${String(count ?? '')}\x1b[0m`);
57
24
  }
@@ -59,5 +26,5 @@ export function applyGlmWebSearchRequestTransform(payload) {
59
26
  // logging best-effort
60
27
  }
61
28
  }
62
- return root;
29
+ return normalized;
63
30
  }
@@ -1,3 +1,5 @@
1
+ import { runReqOutboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
2
+ import { buildIflowRequestCompatInput } from './iflow-native-compat.js';
1
3
  function isRecord(value) {
2
4
  return typeof value === 'object' && value !== null && !Array.isArray(value);
3
5
  }
@@ -8,46 +10,6 @@ function isKimiK25Model(value) {
8
10
  const model = normalizeModel(value);
9
11
  return model === 'kimi-k2.5' || model.startsWith('kimi-k2.5-');
10
12
  }
11
- function mirrorMaxTokens(record) {
12
- const maxTokens = record.max_tokens;
13
- if (typeof maxTokens === 'number' && Number.isFinite(maxTokens) && maxTokens > 0) {
14
- record.max_new_tokens = Math.floor(maxTokens);
15
- }
16
- }
17
- function isThinkingExplicitlyDisabled(value) {
18
- if (value === false) {
19
- return true;
20
- }
21
- if (!isRecord(value)) {
22
- return false;
23
- }
24
- const enabled = value.enabled;
25
- if (enabled === false) {
26
- return true;
27
- }
28
- const type = typeof value.type === 'string' ? value.type.trim().toLowerCase() : '';
29
- return type === 'disabled' || type === 'off';
30
- }
31
- function normalizeThinkingForKimi(record) {
32
- const current = record.thinking;
33
- if (current === undefined || current === null) {
34
- record.thinking = { type: 'enabled' };
35
- return true;
36
- }
37
- if (current === true) {
38
- record.thinking = { type: 'enabled' };
39
- return true;
40
- }
41
- if (isThinkingExplicitlyDisabled(current)) {
42
- return false;
43
- }
44
- if (isRecord(current)) {
45
- if (typeof current.type !== 'string' || current.type.trim().length === 0) {
46
- current.type = 'enabled';
47
- }
48
- }
49
- return true;
50
- }
51
13
  /**
52
14
  * iFlow/Kimi request defaults aligned with iflow-cli:
53
15
  * - thinking enabled path: temperature=1
@@ -58,21 +20,10 @@ function normalizeThinkingForKimi(record) {
58
20
  */
59
21
  export function applyIflowKimiCliDefaults(payload) {
60
22
  try {
61
- if (!isRecord(payload)) {
23
+ if (!isRecord(payload) || !isKimiK25Model(payload.model)) {
62
24
  return payload;
63
25
  }
64
- const root = structuredClone(payload);
65
- if (!isKimiK25Model(root.model)) {
66
- return root;
67
- }
68
- const thinkingEnabled = normalizeThinkingForKimi(root);
69
- root.temperature = thinkingEnabled ? 1 : 0.6;
70
- root.top_p = 0.95;
71
- root.n = 1;
72
- root.presence_penalty = 0;
73
- root.frequency_penalty = 0;
74
- mirrorMaxTokens(root);
75
- return root;
26
+ return runReqOutboundStage3CompatWithNative(buildIflowRequestCompatInput(payload)).payload;
76
27
  }
77
28
  catch {
78
29
  return payload;
@@ -1,88 +1,8 @@
1
+ import { runReqOutboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
2
+ import { buildIflowRequestCompatInput } from './iflow-native-compat.js';
1
3
  function isRecord(value) {
2
4
  return typeof value === 'object' && value !== null && !Array.isArray(value);
3
5
  }
4
- function detectMediaKind(part) {
5
- const typeValue = typeof part.type === 'string' ? part.type.trim().toLowerCase() : '';
6
- if (!typeValue) {
7
- return null;
8
- }
9
- if (typeValue.includes('video')) {
10
- return 'video';
11
- }
12
- if (typeValue.includes('image')) {
13
- return 'image';
14
- }
15
- return null;
16
- }
17
- function isInlineBase64(value) {
18
- const normalized = value.trim().toLowerCase();
19
- if (!normalized) {
20
- return false;
21
- }
22
- if (normalized.startsWith('data:') && normalized.includes(';base64,')) {
23
- return true;
24
- }
25
- if (normalized.startsWith('base64,')) {
26
- return true;
27
- }
28
- return false;
29
- }
30
- function mediaPartCarriesInlineBase64(part) {
31
- const candidates = [];
32
- const imageUrl = part.image_url;
33
- const videoUrl = part.video_url;
34
- if (typeof imageUrl === 'string') {
35
- candidates.push(imageUrl);
36
- }
37
- if (typeof videoUrl === 'string') {
38
- candidates.push(videoUrl);
39
- }
40
- if (isRecord(imageUrl)) {
41
- const url = typeof imageUrl.url === 'string' ? imageUrl.url : '';
42
- const data = typeof imageUrl.data === 'string' ? String(imageUrl.data) : '';
43
- const base64 = typeof imageUrl.base64 === 'string'
44
- ? String(imageUrl.base64)
45
- : '';
46
- if (url)
47
- candidates.push(url);
48
- if (data)
49
- candidates.push(data);
50
- if (base64)
51
- candidates.push(`base64,${base64}`);
52
- }
53
- if (isRecord(videoUrl)) {
54
- const url = typeof videoUrl.url === 'string' ? videoUrl.url : '';
55
- const data = typeof videoUrl.data === 'string' ? String(videoUrl.data) : '';
56
- const base64 = typeof videoUrl.base64 === 'string'
57
- ? String(videoUrl.base64)
58
- : '';
59
- if (url)
60
- candidates.push(url);
61
- if (data)
62
- candidates.push(data);
63
- if (base64)
64
- candidates.push(`base64,${base64}`);
65
- }
66
- const url = typeof part.url === 'string' ? String(part.url) : '';
67
- const uri = typeof part.uri === 'string' ? String(part.uri) : '';
68
- const data = typeof part.data === 'string' ? String(part.data) : '';
69
- const base64 = typeof part.base64 === 'string' ? String(part.base64) : '';
70
- if (url)
71
- candidates.push(url);
72
- if (uri)
73
- candidates.push(uri);
74
- if (data)
75
- candidates.push(data);
76
- if (base64)
77
- candidates.push(`base64,${base64}`);
78
- return candidates.some((value) => isInlineBase64(value));
79
- }
80
- function buildPlaceholderPart(kind) {
81
- return {
82
- type: 'text',
83
- text: kind === 'video' ? '[history_video_base64_omitted]' : '[history_image_base64_omitted]'
84
- };
85
- }
86
6
  /**
87
7
  * iFlow/Kimi multimodal compat:
88
8
  * - Preserve latest user turn media payload.
@@ -93,67 +13,11 @@ export function applyIflowKimiHistoryMediaPlaceholder(payload) {
93
13
  if (!isRecord(payload)) {
94
14
  return payload;
95
15
  }
96
- const root = structuredClone(payload);
97
- const model = typeof root.model === 'string' ? root.model.trim().toLowerCase() : '';
16
+ const model = typeof payload.model === 'string' ? payload.model.trim().toLowerCase() : '';
98
17
  if (model !== 'kimi-k2.5') {
99
- return root;
100
- }
101
- const messagesValue = root.messages;
102
- if (!Array.isArray(messagesValue) || !messagesValue.length) {
103
- return root;
104
- }
105
- const messages = messagesValue.map((item) => (isRecord(item) ? item : null));
106
- let latestUserIndex = -1;
107
- for (let index = messages.length - 1; index >= 0; index -= 1) {
108
- const message = messages[index];
109
- if (!message)
110
- continue;
111
- const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
112
- if (role === 'user') {
113
- latestUserIndex = index;
114
- break;
115
- }
116
- }
117
- if (latestUserIndex <= 0) {
118
- return root;
119
- }
120
- const nextMessages = [];
121
- for (let index = 0; index < messages.length; index += 1) {
122
- const message = messages[index];
123
- if (!message)
124
- continue;
125
- if (index >= latestUserIndex) {
126
- nextMessages.push(message);
127
- continue;
128
- }
129
- const contentValue = message.content;
130
- if (!Array.isArray(contentValue)) {
131
- nextMessages.push(message);
132
- continue;
133
- }
134
- let changed = false;
135
- const nextContent = contentValue.map((part) => {
136
- if (!isRecord(part)) {
137
- return part;
138
- }
139
- const mediaKind = detectMediaKind(part);
140
- if (!mediaKind) {
141
- return part;
142
- }
143
- if (!mediaPartCarriesInlineBase64(part)) {
144
- return part;
145
- }
146
- changed = true;
147
- return buildPlaceholderPart(mediaKind);
148
- });
149
- if (!changed) {
150
- nextMessages.push(message);
151
- continue;
152
- }
153
- nextMessages.push({ ...message, content: nextContent });
18
+ return payload;
154
19
  }
155
- root.messages = nextMessages;
156
- return root;
20
+ return runReqOutboundStage3CompatWithNative(buildIflowRequestCompatInput(payload)).payload;
157
21
  }
158
22
  catch {
159
23
  return payload;
@@ -1,9 +1,8 @@
1
+ import { runReqOutboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
2
+ import { buildIflowRequestCompatInput } from './iflow-native-compat.js';
1
3
  function isRecord(value) {
2
4
  return typeof value === 'object' && value !== null && !Array.isArray(value);
3
5
  }
4
- function hasNonEmptyArray(value) {
5
- return Array.isArray(value) && value.length > 0;
6
- }
7
6
  function isThinkingDisabled(value) {
8
7
  if (value === false)
9
8
  return true;
@@ -32,34 +31,14 @@ export function fillIflowKimiThinkingReasoningContent(payload) {
32
31
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
33
32
  return payload;
34
33
  }
35
- const root = structuredClone(payload);
36
- const model = typeof root.model === 'string' ? root.model.trim().toLowerCase() : '';
34
+ const model = typeof payload.model === 'string' ? payload.model.trim().toLowerCase() : '';
37
35
  if (!(model === 'kimi-k2.5' || model.startsWith('kimi-k2.5-'))) {
38
- return root;
39
- }
40
- if (isThinkingDisabled(root.thinking)) {
41
- return root;
42
- }
43
- const messages = Array.isArray(root.messages) ? root.messages : [];
44
- if (!messages.length) {
45
- return root;
36
+ return payload;
46
37
  }
47
- for (const msg of messages) {
48
- if (!isRecord(msg))
49
- continue;
50
- const role = typeof msg.role === 'string' ? msg.role.trim().toLowerCase() : '';
51
- if (role !== 'assistant')
52
- continue;
53
- if (!hasNonEmptyArray(msg.tool_calls))
54
- continue;
55
- const rc = msg.reasoning_content;
56
- const rcText = typeof rc === 'string' ? rc : '';
57
- if (!rcText || !rcText.trim()) {
58
- msg.reasoning_content = '.';
59
- }
38
+ if (isThinkingDisabled(payload.thinking)) {
39
+ return payload;
60
40
  }
61
- root.messages = messages;
62
- return root;
41
+ return runReqOutboundStage3CompatWithNative(buildIflowRequestCompatInput(payload)).payload;
63
42
  }
64
43
  catch {
65
44
  return payload;
@@ -0,0 +1,6 @@
1
+ import type { AdapterContext } from '../../hub/types/chat-envelope.js';
2
+ import type { JsonObject } from '../../hub/types/json.js';
3
+ import type { NativeReqOutboundCompatAdapterContextInput, NativeReqOutboundStage3CompatInput, NativeRespInboundStage3CompatInput } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
4
+ export declare function buildIflowCompatContext(adapterContext?: AdapterContext): NativeReqOutboundCompatAdapterContextInput;
5
+ export declare function buildIflowRequestCompatInput(payload: JsonObject, adapterContext?: AdapterContext): NativeReqOutboundStage3CompatInput;
6
+ export declare function buildIflowResponseCompatInput(payload: JsonObject, adapterContext?: AdapterContext): NativeRespInboundStage3CompatInput;
@@ -0,0 +1,36 @@
1
+ import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
2
+ const PROFILE = 'chat:iflow';
3
+ const DEFAULT_PROVIDER_PROTOCOL = 'openai-chat';
4
+ const DEFAULT_ENTRY_ENDPOINT = '/v1/chat/completions';
5
+ export function buildIflowCompatContext(adapterContext) {
6
+ const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
7
+ const adapterProfile = adapterContext && typeof adapterContext['compatibilityProfile'] === 'string'
8
+ ? String(adapterContext['compatibilityProfile']).trim() || undefined
9
+ : undefined;
10
+ return {
11
+ ...nativeContext,
12
+ compatibilityProfile: nativeContext.compatibilityProfile ??
13
+ adapterProfile ??
14
+ PROFILE,
15
+ providerProtocol: nativeContext.providerProtocol ??
16
+ adapterContext?.providerProtocol ??
17
+ DEFAULT_PROVIDER_PROTOCOL,
18
+ entryEndpoint: nativeContext.entryEndpoint ??
19
+ adapterContext?.entryEndpoint ??
20
+ DEFAULT_ENTRY_ENDPOINT
21
+ };
22
+ }
23
+ export function buildIflowRequestCompatInput(payload, adapterContext) {
24
+ return {
25
+ payload,
26
+ adapterContext: buildIflowCompatContext(adapterContext),
27
+ explicitProfile: PROFILE
28
+ };
29
+ }
30
+ export function buildIflowResponseCompatInput(payload, adapterContext) {
31
+ return {
32
+ payload,
33
+ adapterContext: buildIflowCompatContext(adapterContext),
34
+ explicitProfile: PROFILE
35
+ };
36
+ }
@@ -1,99 +1,6 @@
1
+ import { runRespInboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
2
+ import { buildIflowResponseCompatInput } from './iflow-native-compat.js';
1
3
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
- function looksLikeKnownProviderResponseShape(value) {
3
- if (!isRecord(value))
4
- return false;
5
- if (Array.isArray(value.choices))
6
- return true; // openai-chat
7
- if (Array.isArray(value.output) || value.object === 'response')
8
- return true; // openai-responses
9
- if (typeof value.type === 'string' && String(value.type).toLowerCase() === 'message' && Array.isArray(value.content)) {
10
- return true; // anthropic-messages
11
- }
12
- if (Array.isArray(value.candidates))
13
- return true; // gemini-chat
14
- return false;
15
- }
16
- function stripJsonTextPrefix(text) {
17
- let out = String(text || '').trimStart();
18
- // anti-XSSI prefix: ")]}',\n{...}"
19
- out = out.replace(/^\)\]\}',?\s*/u, '');
20
- // single-line SSE-like wrapper sometimes returned as plain text
21
- out = out.replace(/^data:\s*/iu, '');
22
- return out;
23
- }
24
- function tryParseJsonRecord(text) {
25
- try {
26
- const trimmed = stripJsonTextPrefix(text).trim();
27
- if (!trimmed)
28
- return null;
29
- if (trimmed.length > 10 * 1024 * 1024)
30
- return null;
31
- const first = trimmed.charAt(0);
32
- const last = trimmed.charAt(trimmed.length - 1);
33
- if ((first !== '{' && first !== '[') || (last !== '}' && last !== ']')) {
34
- return null;
35
- }
36
- const parsed = JSON.parse(trimmed);
37
- return isRecord(parsed) ? parsed : null;
38
- }
39
- catch {
40
- return null;
41
- }
42
- }
43
- function tryParseJsonRecordFromMaybeSseText(text) {
44
- if (typeof text !== 'string') {
45
- return null;
46
- }
47
- const raw = stripJsonTextPrefix(text);
48
- const direct = tryParseJsonRecord(raw);
49
- if (direct) {
50
- return direct;
51
- }
52
- // Multi-line SSE text occasionally shows up as a plain string inside iFlow envelopes.
53
- // Best-effort: parse the last valid `data:` JSON object chunk.
54
- if (raw.length > 10 * 1024 * 1024) {
55
- return null;
56
- }
57
- const lines = raw.split(/\r?\n/u);
58
- for (let i = lines.length - 1; i >= 0; i -= 1) {
59
- const line = lines[i]?.trim();
60
- if (!line)
61
- continue;
62
- const lower = line.toLowerCase();
63
- if (lower === 'data: [done]' || lower === '[done]')
64
- continue;
65
- const candidate = lower.startsWith('data:') ? line.slice(5).trim() : line;
66
- const parsed = tryParseJsonRecord(candidate);
67
- if (parsed) {
68
- return parsed;
69
- }
70
- }
71
- return null;
72
- }
73
- function tryUnwrapKnownShapeFromBody(value, depth) {
74
- if (depth < 0)
75
- return null;
76
- if (looksLikeKnownProviderResponseShape(value)) {
77
- return value;
78
- }
79
- if (isRecord(value)) {
80
- // nested body/data wrappers
81
- for (const key of ['data', 'body', 'response', 'payload', 'result']) {
82
- const nested = value[key];
83
- const unwrapped = tryUnwrapKnownShapeFromBody(nested, depth - 1);
84
- if (unwrapped)
85
- return unwrapped;
86
- }
87
- return null;
88
- }
89
- if (typeof value === 'string') {
90
- const parsed = tryParseJsonRecord(value) ?? tryParseJsonRecordFromMaybeSseText(value);
91
- if (!parsed)
92
- return null;
93
- return tryUnwrapKnownShapeFromBody(parsed, depth - 1);
94
- }
95
- return null;
96
- }
97
4
  /**
98
5
  * iFlow compatibility: some iFlow backends wrap OpenAI-compatible payloads inside:
99
6
  * { status, msg, body, request_id }
@@ -107,32 +14,10 @@ export function unwrapIflowResponseBodyEnvelope(payload) {
107
14
  return payload;
108
15
  }
109
16
  const root = structuredClone(payload);
110
- // Only unwrap when it looks like the iFlow envelope.
111
17
  if (!('body' in root) || !('status' in root) || !('msg' in root)) {
112
- return root;
113
- }
114
- // If the root is already a known provider response, don't touch it.
115
- if (looksLikeKnownProviderResponseShape(root)) {
116
- return root;
117
- }
118
- const body = root.body;
119
- const unwrapped = tryUnwrapKnownShapeFromBody(body, 6);
120
- if (unwrapped) {
121
- return unwrapped;
122
- }
123
- // Fallback: unwrap to `body` even when we can't yet recognize the nested shape.
124
- // This avoids treating the iFlow envelope as the provider payload and improves
125
- // downstream diagnostics / error handling.
126
- if (isRecord(body)) {
127
- return body;
128
- }
129
- if (typeof body === 'string') {
130
- const parsed = tryParseJsonRecordFromMaybeSseText(body);
131
- if (parsed) {
132
- return parsed;
133
- }
18
+ return payload;
134
19
  }
135
- return root;
20
+ return runRespInboundStage3CompatWithNative(buildIflowResponseCompatInput(payload)).payload;
136
21
  }
137
22
  catch {
138
23
  return payload;
@@ -1,3 +1,5 @@
1
+ import { runReqOutboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
2
+ import { buildIflowRequestCompatInput } from './iflow-native-compat.js';
1
3
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
4
  const DEBUG_IFLOW_WEB_SEARCH = (process.env.ROUTECODEX_DEBUG_IFLOW_WEB_SEARCH || '').trim() === '1';
3
5
  /**
@@ -18,63 +20,20 @@ const DEBUG_IFLOW_WEB_SEARCH = (process.env.ROUTECODEX_DEBUG_IFLOW_WEB_SEARCH ||
18
20
  export function applyIflowWebSearchRequestTransform(payload, adapterContext) {
19
21
  const routeId = typeof adapterContext?.routeId === 'string' ? adapterContext.routeId : '';
20
22
  const normalizedRoute = routeId.trim().toLowerCase();
21
- // Some hosts/configs use "search" as the route name for web search engines.
22
- // Treat both "web_search*" and "search*" as web-search routes.
23
23
  if (!normalizedRoute || (!normalizedRoute.startsWith('web_search') && !normalizedRoute.startsWith('search'))) {
24
24
  return payload;
25
25
  }
26
- const root = structuredClone(payload);
27
- const webSearchRaw = root.web_search;
28
- if (!isRecord(webSearchRaw)) {
29
- return root;
30
- }
31
- const webSearch = webSearchRaw;
32
- const queryValue = webSearch.query;
33
- const recencyValue = webSearch.recency;
34
- const countValue = webSearch.count;
35
- const query = typeof queryValue === 'string' ? queryValue.trim() : '';
36
- const recency = typeof recencyValue === 'string' ? recencyValue.trim() : undefined;
37
- let count;
38
- if (typeof countValue === 'number' && Number.isFinite(countValue)) {
39
- const normalized = Math.floor(countValue);
40
- if (normalized >= 1 && normalized <= 50) {
41
- count = normalized;
42
- }
43
- }
44
- if (!query) {
45
- // No meaningful search query, drop the helper object and passthrough.
46
- delete root.web_search;
47
- return root;
48
- }
49
- const tool = {
50
- type: 'function',
51
- function: {
52
- name: 'web_search',
53
- description: 'Perform web search over the public internet and return up-to-date results.',
54
- parameters: {
55
- type: 'object',
56
- properties: {
57
- query: {
58
- type: 'string',
59
- description: 'Search query string.'
60
- },
61
- recency: {
62
- type: 'string',
63
- description: 'Optional recency filter such as "day", "week", or "month".'
64
- },
65
- count: {
66
- type: 'integer',
67
- minimum: 1,
68
- maximum: 50,
69
- description: 'Maximum number of search results to retrieve (1-50).'
70
- }
71
- },
72
- required: ['query']
73
- }
74
- }
75
- };
76
- root.tools = [tool];
77
- delete root.web_search;
26
+ const normalized = runReqOutboundStage3CompatWithNative(buildIflowRequestCompatInput(payload, adapterContext)).payload;
27
+ const query = isRecord(payload) && isRecord(payload.web_search)
28
+ ? typeof (payload.web_search?.query) === 'string'
29
+ ? String(payload.web_search?.query).trim()
30
+ : ''
31
+ : '';
32
+ const recency = isRecord(payload) && isRecord(payload.web_search)
33
+ ? typeof (payload.web_search?.recency) === 'string'
34
+ ? String(payload.web_search?.recency).trim()
35
+ : undefined
36
+ : undefined;
78
37
  if (DEBUG_IFLOW_WEB_SEARCH) {
79
38
  try {
80
39
  // eslint-disable-next-line no-console
@@ -86,5 +45,5 @@ export function applyIflowWebSearchRequestTransform(payload, adapterContext) {
86
45
  // logging best-effort
87
46
  }
88
47
  }
89
- return root;
48
+ return normalized;
90
49
  }