@jsonstudio/llms 0.6.3551 → 0.6.3686
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/compat/actions/antigravity-thought-signature-cache.js +4 -115
- package/dist/conversion/compat/actions/auto-thinking.js +3 -2
- package/dist/conversion/compat/actions/deepseek-web-response.js +15 -49
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +1 -1
- package/dist/conversion/compat/actions/glm-history-image-trim.js +3 -37
- package/dist/conversion/compat/actions/glm-image-content.js +3 -32
- package/dist/conversion/compat/actions/glm-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/glm-native-compat.js +34 -0
- package/dist/conversion/compat/actions/glm-vision-prompt.js +3 -76
- package/dist/conversion/compat/actions/glm-web-search.js +10 -43
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +4 -53
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +5 -141
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +7 -28
- package/dist/conversion/compat/actions/iflow-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/iflow-native-compat.js +36 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +4 -119
- package/dist/conversion/compat/actions/iflow-web-search.js +14 -55
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +5 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +31 -18
- package/dist/conversion/hub/pipeline/hub-pipeline.js +163 -163
- package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +6 -0
- package/dist/conversion/hub/pipeline/hub-stage-timing.js +178 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +4 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +33 -14
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -6
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +41 -23
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +44 -1
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -1
- package/dist/conversion/hub/process/chat-process-continue-execution.js +5 -4
- package/dist/conversion/hub/process/chat-process-governance-orchestration.js +3 -1
- package/dist/conversion/hub/process/chat-process-media.d.ts +3 -1
- package/dist/conversion/hub/process/chat-process-media.js +92 -2
- package/dist/conversion/hub/process/chat-process-session-usage.d.ts +7 -0
- package/dist/conversion/hub/process/chat-process-session-usage.js +147 -0
- package/dist/conversion/hub/response/provider-response.js +13 -0
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +0 -12
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -9
- package/dist/conversion/responses/responses-openai-bridge.js +77 -44
- package/dist/conversion/shared/reasoning-normalizer.js +42 -0
- package/dist/conversion/shared/responses-tool-utils.js +2 -3
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +6 -0
- package/dist/router/virtual-router/bootstrap.js +1 -6
- package/dist/router/virtual-router/engine-legacy.js +43 -0
- package/dist/router/virtual-router/engine-logging.d.ts +3 -0
- package/dist/router/virtual-router/engine-logging.js +29 -3
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.d.ts +2 -2
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.js +96 -80
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine.js +34 -22
- package/dist/router/virtual-router/provider-registry.js +1 -0
- package/dist/router/virtual-router/routing-instructions/state.js +35 -3
- package/dist/router/virtual-router/routing-instructions/types.d.ts +4 -0
- package/dist/router/virtual-router/types.d.ts +7 -0
- package/dist/servertool/engine.js +3 -34
- package/dist/servertool/handlers/followup-request-builder.js +0 -6
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +3 -274
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +0 -3
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +0 -29
- package/dist/servertool/handlers/stop-message-auto.js +2 -10
- package/dist/servertool/handlers/vision.js +4 -1
- package/dist/servertool/server-side-tools.js +66 -3
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
48
|
+
return normalized;
|
|
90
49
|
}
|
|
@@ -2,6 +2,11 @@ import { isJsonObject, jsonClone } from '../../../types/json.js';
|
|
|
2
2
|
import { normalizeChatMessageContentWithNative, normalizeOpenaiChatMessagesWithNative } from '../../../../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
|
|
3
3
|
import { ensureProtocolState } from '../../../../protocol-state.js';
|
|
4
4
|
import { mapReqInboundBridgeToolsToChatWithNative } from '../../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
|
|
5
|
+
const ALLOW_ARCHIVE_IMPORTS = process.env.LLMSWITCH_ALLOW_ARCHIVE_IMPORTS === '1' ||
|
|
6
|
+
process.env.ROUTECODEX_ALLOW_ARCHIVE_IMPORTS === '1';
|
|
7
|
+
if (!ALLOW_ARCHIVE_IMPORTS) {
|
|
8
|
+
throw new Error('[archive] chat-mapper.archive is fail-closed. Set LLMSWITCH_ALLOW_ARCHIVE_IMPORTS=1 only for explicit parity/compare scripts.');
|
|
9
|
+
}
|
|
5
10
|
const CHAT_PARAMETER_KEYS = [
|
|
6
11
|
'model',
|
|
7
12
|
'temperature',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isJsonObject, jsonClone } from '../../types/json.js';
|
|
2
2
|
import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesRequestFromChat } from '../../../responses/responses-openai-bridge.js';
|
|
3
|
+
import { logHubStageTiming } from '../../pipeline/hub-stage-timing.js';
|
|
3
4
|
import { maybeAugmentApplyPatchErrorContent } from './chat-mapper.js';
|
|
4
5
|
import { mapReqInboundBridgeToolsToChatWithNative, mapReqInboundResumeToolOutputsDetailedWithNative } from '../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
|
|
5
6
|
const RESPONSES_PARAMETER_KEYS = [
|
|
@@ -337,29 +338,19 @@ function collectSubmitToolOutputs(chat, responsesContext) {
|
|
|
337
338
|
}
|
|
338
339
|
return outputs;
|
|
339
340
|
}
|
|
340
|
-
function resolveSubmitStreamFlag(chat, _ctx,
|
|
341
|
+
function resolveSubmitStreamFlag(chat, _ctx, _responsesContext) {
|
|
341
342
|
if (chat.parameters && typeof chat.parameters.stream === 'boolean') {
|
|
342
343
|
return chat.parameters.stream;
|
|
343
344
|
}
|
|
344
|
-
if (responsesContext && typeof responsesContext.stream === 'boolean') {
|
|
345
|
-
return responsesContext.stream;
|
|
346
|
-
}
|
|
347
345
|
return undefined;
|
|
348
346
|
}
|
|
349
|
-
function resolveSubmitModel(chat,
|
|
347
|
+
function resolveSubmitModel(chat, _responsesContext) {
|
|
350
348
|
const direct = chat.parameters && typeof chat.parameters.model === 'string'
|
|
351
349
|
? chat.parameters.model.trim()
|
|
352
350
|
: undefined;
|
|
353
351
|
if (direct) {
|
|
354
352
|
return direct;
|
|
355
353
|
}
|
|
356
|
-
if (responsesContext && typeof responsesContext.parameters === 'object') {
|
|
357
|
-
const params = responsesContext.parameters;
|
|
358
|
-
const model = typeof params.model === 'string' ? params.model.trim() : undefined;
|
|
359
|
-
if (model) {
|
|
360
|
-
return model;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
354
|
return undefined;
|
|
364
355
|
}
|
|
365
356
|
function buildSubmitToolOutputsPayload(chat, ctx, responsesContext) {
|
|
@@ -400,13 +391,26 @@ function buildSubmitToolOutputsPayload(chat, ctx, responsesContext) {
|
|
|
400
391
|
}
|
|
401
392
|
export class ResponsesSemanticMapper {
|
|
402
393
|
async toChat(format, ctx) {
|
|
394
|
+
const requestId = typeof ctx.requestId === 'string' && ctx.requestId.trim().length ? ctx.requestId : 'unknown';
|
|
403
395
|
const payload = format.payload || {};
|
|
396
|
+
logHubStageTiming(requestId, 'req_inbound.responses.capture_context', 'start');
|
|
397
|
+
const captureStart = Date.now();
|
|
404
398
|
const responsesContext = captureResponsesContext(payload, { route: { requestId: ctx.requestId } });
|
|
399
|
+
logHubStageTiming(requestId, 'req_inbound.responses.capture_context', 'completed', {
|
|
400
|
+
elapsedMs: Date.now() - captureStart,
|
|
401
|
+
forceLog: true
|
|
402
|
+
});
|
|
403
|
+
logHubStageTiming(requestId, 'req_inbound.responses.build_chat_request', 'start');
|
|
404
|
+
const buildChatStart = Date.now();
|
|
405
405
|
const { request, toolsNormalized } = buildChatRequestFromResponses(payload, responsesContext);
|
|
406
|
+
logHubStageTiming(requestId, 'req_inbound.responses.build_chat_request', 'completed', {
|
|
407
|
+
elapsedMs: Date.now() - buildChatStart,
|
|
408
|
+
forceLog: true
|
|
409
|
+
});
|
|
406
410
|
const missingFields = [];
|
|
407
411
|
const messages = normalizeMessages(request.messages, missingFields);
|
|
408
412
|
let toolOutputs = mapToolOutputs(payload.tool_outputs, missingFields);
|
|
409
|
-
const parameters = collectParameters(payload,
|
|
413
|
+
const parameters = collectParameters(payload, typeof payload.stream === 'boolean' ? payload.stream : undefined);
|
|
410
414
|
const metadata = { context: ctx };
|
|
411
415
|
if (missingFields.length) {
|
|
412
416
|
metadata.missingFields = missingFields;
|
|
@@ -423,6 +427,7 @@ export class ResponsesSemanticMapper {
|
|
|
423
427
|
};
|
|
424
428
|
}
|
|
425
429
|
async fromChat(chat, ctx) {
|
|
430
|
+
const requestId = typeof ctx.requestId === 'string' && ctx.requestId.trim().length ? ctx.requestId : 'unknown';
|
|
426
431
|
const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
|
|
427
432
|
const responsesContext = selectResponsesContextSnapshot(chat, envelopeMetadata);
|
|
428
433
|
if (isSubmitToolOutputsEndpoint(ctx)) {
|
|
@@ -447,12 +452,20 @@ export class ResponsesSemanticMapper {
|
|
|
447
452
|
messages: chat.messages,
|
|
448
453
|
tools: chat.tools
|
|
449
454
|
};
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
+
if (!Array.isArray(responsesContext.originalSystemMessages) || responsesContext.originalSystemMessages.length === 0) {
|
|
456
|
+
const originalSystemMessages = (chat.messages || [])
|
|
457
|
+
.filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
|
|
458
|
+
.map(message => serializeSystemContent(message))
|
|
459
|
+
.filter((content) => typeof content === 'string' && content.length > 0);
|
|
460
|
+
responsesContext.originalSystemMessages = originalSystemMessages;
|
|
461
|
+
}
|
|
462
|
+
logHubStageTiming(requestId, 'req_outbound.responses.build_request', 'start');
|
|
463
|
+
const buildRequestStart = Date.now();
|
|
455
464
|
const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
|
|
465
|
+
logHubStageTiming(requestId, 'req_outbound.responses.build_request', 'completed', {
|
|
466
|
+
elapsedMs: Date.now() - buildRequestStart,
|
|
467
|
+
forceLog: true
|
|
468
|
+
});
|
|
456
469
|
const responses = responsesResult.request;
|
|
457
470
|
if (chat.parameters && chat.parameters.stream !== undefined) {
|
|
458
471
|
responses.stream = chat.parameters.stream;
|