@jsonstudio/llms 0.6.3409 → 0.6.3541
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/codecs/anthropic-openai-codec.d.ts +12 -3
- package/dist/conversion/codecs/anthropic-openai-codec.js +32 -92
- package/dist/conversion/codecs/gemini-openai-codec.d.ts +6 -5
- package/dist/conversion/codecs/gemini-openai-codec.js +48 -685
- package/dist/conversion/codecs/openai-openai-codec.d.ts +1 -1
- package/dist/conversion/codecs/openai-openai-codec.js +34 -100
- package/dist/conversion/codecs/responses-openai-codec.d.ts +1 -1
- package/dist/conversion/codecs/responses-openai-codec.js +47 -159
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +2 -6
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +29 -245
- package/dist/conversion/compat/actions/anthropic-claude-code-user-id.d.ts +3 -0
- package/dist/conversion/compat/actions/anthropic-claude-code-user-id.js +30 -0
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +21 -232
- package/dist/conversion/compat/actions/deepseek-web-request.js +41 -276
- package/dist/conversion/compat/actions/deepseek-web-response.js +117 -855
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +1 -1
- package/dist/conversion/compat/actions/gemini-cli-request.js +20 -613
- package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -15
- package/dist/conversion/compat/actions/gemini-web-search.js +22 -69
- package/dist/conversion/compat/actions/glm-tool-extraction.d.ts +3 -2
- package/dist/conversion/compat/actions/glm-tool-extraction.js +28 -257
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +0 -8
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +24 -206
- package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -2
- package/dist/conversion/compat/actions/qwen-transform.js +30 -271
- package/dist/conversion/compat/actions/tool-text-request-guidance.js +3 -173
- package/dist/conversion/compat/actions/universal-shape-filter.d.ts +6 -23
- package/dist/conversion/compat/actions/universal-shape-filter.js +4 -383
- package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +1 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +1 -2
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +50 -104
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +12 -10
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +0 -2
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +46 -67
- package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +15 -40
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +47 -348
- package/dist/conversion/responses/responses-openai-bridge.js +129 -611
- package/dist/conversion/shared/chat-output-normalizer.js +6 -0
- package/dist/conversion/shared/chat-request-filters.js +1 -1
- package/dist/conversion/shared/output-content-normalizer.js +10 -0
- package/dist/conversion/shared/responses-conversation-store.js +22 -135
- package/dist/conversion/shared/responses-output-builder.d.ts +0 -2
- package/dist/conversion/shared/responses-output-builder.js +28 -318
- package/dist/conversion/shared/responses-response-utils.js +35 -86
- package/dist/conversion/shared/streaming-text-extractor.d.ts +1 -2
- package/dist/conversion/shared/streaming-text-extractor.js +13 -14
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/quota/quota-state.js +29 -7
- package/dist/quota/types.d.ts +1 -0
- package/dist/router/virtual-router/bootstrap/routing-config.js +11 -3
- package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
- package/dist/router/virtual-router/engine-legacy.js +15 -7
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
- package/dist/router/virtual-router/engine.js +0 -38
- package/dist/router/virtual-router/features.js +44 -3
- package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
- package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
- package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
- package/dist/servertool/handlers/followup-request-builder.js +12 -2
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
- package/package.json +1 -1
- package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
- package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
- package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
- package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
- package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
- package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
- package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
- package/dist/router/virtual-router/engine-legacy/routing.js +0 -8
|
@@ -1,71 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return payload;
|
|
23
|
-
}
|
|
24
|
-
const cloned = { ...payload };
|
|
25
|
-
// 对于 Gemini Chat 后端,请求根节点不支持自定义 `web_search` 字段,
|
|
26
|
-
// 统一在 compat 层移除,避免触发
|
|
27
|
-
// "Unknown name \"web_search\" at 'request': Cannot find field." 错误。
|
|
28
|
-
if (cloned.web_search !== undefined) {
|
|
29
|
-
delete cloned.web_search;
|
|
30
|
-
}
|
|
31
|
-
const toolsRaw = cloned.tools;
|
|
32
|
-
// 当 web_search 路由下完全没有 tools 时,为 Gemini 搜索模型注入最小 googleSearch 工具,
|
|
33
|
-
// 保持与 gcli2api 类似的“搜索模型自动启用 Search 工具”行为。
|
|
34
|
-
if (!Array.isArray(toolsRaw)) {
|
|
35
|
-
cloned.tools = [{ googleSearch: {} }];
|
|
36
|
-
return cloned;
|
|
37
|
-
}
|
|
38
|
-
const nextTools = [];
|
|
39
|
-
for (const entry of toolsRaw) {
|
|
40
|
-
if (!isRecord(entry))
|
|
41
|
-
continue;
|
|
42
|
-
// 1) 保留 name === 'web_search' 的 functionDeclarations(单函数声明)。
|
|
43
|
-
const decls = Array.isArray(entry.functionDeclarations)
|
|
44
|
-
? entry.functionDeclarations
|
|
45
|
-
: [];
|
|
46
|
-
const webSearchDecls = decls.filter((fn) => {
|
|
47
|
-
if (!isRecord(fn))
|
|
48
|
-
return false;
|
|
49
|
-
const name = typeof fn.name === 'string' ? fn.name.trim().toLowerCase() : '';
|
|
50
|
-
return name === 'web_search';
|
|
51
|
-
});
|
|
52
|
-
if (webSearchDecls.length) {
|
|
53
|
-
nextTools.push({
|
|
54
|
-
functionDeclarations: webSearchDecls
|
|
55
|
-
});
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
// 2) 若上游已经构造了 googleSearch 工具,则保留。
|
|
59
|
-
if (isRecord(entry.googleSearch)) {
|
|
60
|
-
nextTools.push({ googleSearch: entry.googleSearch });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (nextTools.length > 0) {
|
|
64
|
-
cloned.tools = nextTools;
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
// 3) 清洗后没有任何工具时,再次注入一个最小 googleSearch 工具,确保搜索模型仍然启用 Search。
|
|
68
|
-
cloned.tools = [{ googleSearch: {} }];
|
|
1
|
+
import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
|
|
2
|
+
import { applyGeminiWebSearchRequestCompatWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
|
|
3
|
+
const PROFILE = 'chat:gemini';
|
|
4
|
+
const DEFAULT_PROVIDER_PROTOCOL = 'gemini-chat';
|
|
5
|
+
const DEFAULT_ENTRY_ENDPOINT = '/v1beta/models:generateContent';
|
|
6
|
+
function buildGeminiCompatContext(adapterContext) {
|
|
7
|
+
const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
|
|
8
|
+
return {
|
|
9
|
+
...nativeContext,
|
|
10
|
+
compatibilityProfile: PROFILE,
|
|
11
|
+
providerProtocol: nativeContext.providerProtocol ??
|
|
12
|
+
adapterContext?.providerProtocol ??
|
|
13
|
+
DEFAULT_PROVIDER_PROTOCOL,
|
|
14
|
+
entryEndpoint: nativeContext.entryEndpoint ??
|
|
15
|
+
adapterContext?.entryEndpoint ??
|
|
16
|
+
DEFAULT_ENTRY_ENDPOINT,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function applyGeminiWebSearchCompat(root, adapterContext) {
|
|
20
|
+
if (!root || typeof root !== 'object' || Array.isArray(root)) {
|
|
21
|
+
return root;
|
|
69
22
|
}
|
|
70
|
-
return
|
|
23
|
+
return applyGeminiWebSearchRequestCompatWithNative(root, buildGeminiCompatContext(adapterContext));
|
|
71
24
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { AdapterContext } from "../../hub/types/chat-envelope.js";
|
|
2
|
+
import type { JsonObject } from "../../hub/types/json.js";
|
|
3
|
+
export declare function extractGlmToolMarkup(root: JsonObject, adapterContext?: AdapterContext): void;
|
|
@@ -1,264 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (Array.isArray(content)) {
|
|
9
|
-
return content.map((entry) => flattenContent(entry, depth + 1)).join('');
|
|
10
|
-
}
|
|
11
|
-
if (typeof content === 'object') {
|
|
12
|
-
const record = content;
|
|
13
|
-
if (typeof record.text === 'string') {
|
|
14
|
-
return record.text;
|
|
15
|
-
}
|
|
16
|
-
if (record.content !== undefined) {
|
|
17
|
-
return flattenContent(record.content, depth + 1);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return '';
|
|
21
|
-
}
|
|
22
|
-
const GLM_CUSTOM_TAG = /<tool_call(?:\s+name="([^"]+)")?>([\s\S]*?)<\/tool_call>/gi;
|
|
23
|
-
const GLM_TAGGED_SEQUENCE = /<tool_call(?:\s+name="([^"]+)")?\s*>([\s\S]*?)(?:<\/tool_call>|(?=<tool_call)|$)/gi;
|
|
24
|
-
const GLM_TAGGED_BLOCK = /<arg_key>([\s\S]*?)<\/arg_key>\s*<arg_value>([\s\S]*?)<\/arg_value>/gi;
|
|
25
|
-
const GLM_INLINE_NAME = /^[\s\r\n]*([A-Za-z0-9_.:-]+)/;
|
|
26
|
-
const GENERIC_PATTERNS = [
|
|
27
|
-
[
|
|
28
|
-
/```(?:tool|function|tool_call|function_call)?\s*([\s\S]*?)\s*```/gi,
|
|
29
|
-
(match) => ({ body: match[1] ?? '' })
|
|
30
|
-
],
|
|
31
|
-
[
|
|
32
|
-
/\[(tool_call|function_call)(?:\s+name="([^"]+)")?\]([\s\S]*?)\[\/\1\]/gi,
|
|
33
|
-
(match) => ({ body: match[3] ?? '', nameHint: match[2] })
|
|
34
|
-
],
|
|
35
|
-
[
|
|
36
|
-
/(tool_call|function_call)\s*[:=]\s*({[\s\S]+?})/gi,
|
|
37
|
-
(match) => ({ body: match[2] ?? '' })
|
|
38
|
-
]
|
|
39
|
-
];
|
|
40
|
-
export function extractGlmToolMarkup(root) {
|
|
41
|
-
const choicesRaw = root?.choices;
|
|
42
|
-
const choices = Array.isArray(choicesRaw) ? choicesRaw : [];
|
|
43
|
-
choices.forEach((choice, index) => {
|
|
44
|
-
if (!choice || typeof choice !== 'object') {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const message = choice.message;
|
|
48
|
-
if (!message || typeof message !== 'object') {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const msgRecord = message;
|
|
52
|
-
const contentField = msgRecord.content;
|
|
53
|
-
const reasoningField = msgRecord.reasoning_content;
|
|
54
|
-
const text = contentField !== undefined
|
|
55
|
-
? flattenContent(contentField)
|
|
56
|
-
: typeof reasoningField === 'string'
|
|
57
|
-
? reasoningField
|
|
58
|
-
: '';
|
|
59
|
-
if (!text) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const extraction = extractToolCallsFromText(text, index + 1);
|
|
63
|
-
if (!extraction) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (extraction.toolCalls.length) {
|
|
67
|
-
msgRecord.tool_calls = extraction.toolCalls;
|
|
68
|
-
if (contentField !== undefined) {
|
|
69
|
-
msgRecord.content = null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (extraction.reasoningText) {
|
|
73
|
-
msgRecord.reasoning_content = extraction.reasoningText;
|
|
74
|
-
}
|
|
75
|
-
else if ('reasoning_content' in msgRecord) {
|
|
76
|
-
delete msgRecord.reasoning_content;
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
function extractToolCallsFromText(text, choiceIndex) {
|
|
81
|
-
const matches = [];
|
|
82
|
-
const applyPattern = (pattern, factory) => {
|
|
83
|
-
pattern.lastIndex = 0;
|
|
84
|
-
let exec;
|
|
85
|
-
while ((exec = pattern.exec(text))) {
|
|
86
|
-
const payload = factory(exec);
|
|
87
|
-
if (!payload)
|
|
88
|
-
continue;
|
|
89
|
-
const parsed = parseToolCall(payload.body, payload.nameHint);
|
|
90
|
-
if (!parsed)
|
|
91
|
-
continue;
|
|
92
|
-
matches.push({
|
|
93
|
-
start: exec.index,
|
|
94
|
-
end: exec.index + exec[0].length,
|
|
95
|
-
call: parsed
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
applyPattern(GLM_CUSTOM_TAG, (match) => ({ body: match[2] ?? '', nameHint: match[1] }));
|
|
100
|
-
for (const [pattern, factory] of GENERIC_PATTERNS) {
|
|
101
|
-
applyPattern(pattern, factory);
|
|
102
|
-
}
|
|
103
|
-
applyTaggedArgPatterns(text, matches);
|
|
104
|
-
if (!matches.length && typeof text === 'string' && text.includes('<arg_key>')) {
|
|
105
|
-
GLM_INLINE_NAME.lastIndex = 0;
|
|
106
|
-
const inline = GLM_INLINE_NAME.exec(text);
|
|
107
|
-
GLM_INLINE_NAME.lastIndex = 0;
|
|
108
|
-
if (inline && inline[1]) {
|
|
109
|
-
const name = inline[1].trim();
|
|
110
|
-
const block = text.slice(inline[0].length);
|
|
111
|
-
const argsRecord = parseTaggedArgBlock(block);
|
|
112
|
-
if (name && argsRecord) {
|
|
113
|
-
matches.push({
|
|
114
|
-
start: 0,
|
|
115
|
-
end: text.length,
|
|
116
|
-
call: {
|
|
117
|
-
name,
|
|
118
|
-
args: safeStringify(argsRecord)
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
matches.sort((a, b) => a.start - b.start);
|
|
125
|
-
const toolCalls = matches.map((entry, idx) => ({
|
|
126
|
-
id: `glm_tool_${choiceIndex}_${idx + 1}`,
|
|
127
|
-
type: 'function',
|
|
128
|
-
function: {
|
|
129
|
-
name: entry.call.name,
|
|
130
|
-
arguments: entry.call.args
|
|
131
|
-
}
|
|
132
|
-
}));
|
|
133
|
-
matches.sort((a, b) => b.start - a.start);
|
|
134
|
-
let cleaned = text;
|
|
135
|
-
for (const entry of matches) {
|
|
136
|
-
cleaned = cleaned.slice(0, entry.start) + cleaned.slice(entry.end);
|
|
137
|
-
}
|
|
138
|
-
const reasoningText = cleaned.trim();
|
|
1
|
+
import { buildNativeReqOutboundCompatAdapterContext } from "../../hub/pipeline/compat/native-adapter-context.js";
|
|
2
|
+
import { runRespInboundStage3CompatWithNative } from "../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js";
|
|
3
|
+
const PROFILE = "chat:glm";
|
|
4
|
+
const DEFAULT_PROVIDER_PROTOCOL = "openai-chat";
|
|
5
|
+
const DEFAULT_ENTRY_ENDPOINT = "/v1/chat/completions";
|
|
6
|
+
function buildGlmCompatContext(adapterContext) {
|
|
7
|
+
const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
|
|
139
8
|
return {
|
|
140
|
-
|
|
141
|
-
|
|
9
|
+
...nativeContext,
|
|
10
|
+
compatibilityProfile: PROFILE,
|
|
11
|
+
providerProtocol: nativeContext.providerProtocol ??
|
|
12
|
+
adapterContext?.providerProtocol ??
|
|
13
|
+
DEFAULT_PROVIDER_PROTOCOL,
|
|
14
|
+
entryEndpoint: nativeContext.entryEndpoint ??
|
|
15
|
+
adapterContext?.entryEndpoint ??
|
|
16
|
+
DEFAULT_ENTRY_ENDPOINT,
|
|
142
17
|
};
|
|
143
18
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
const parsed = JSON.parse(trimmed);
|
|
154
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
const record = parsed;
|
|
158
|
-
const candidateName = (typeof record.name === 'string' && record.name.trim().length
|
|
159
|
-
? record.name.trim()
|
|
160
|
-
: undefined) ??
|
|
161
|
-
(typeof record.tool_name === 'string' && record.tool_name.trim().length
|
|
162
|
-
? record.tool_name.trim()
|
|
163
|
-
: undefined) ??
|
|
164
|
-
(typeof record.tool === 'string' && record.tool.trim().length
|
|
165
|
-
? record.tool.trim()
|
|
166
|
-
: undefined) ??
|
|
167
|
-
(nameHint && nameHint.trim().length ? nameHint.trim() : undefined);
|
|
168
|
-
if (!candidateName) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
const argsSource = record.arguments ??
|
|
172
|
-
record.input ??
|
|
173
|
-
record.params ??
|
|
174
|
-
record.parameters ??
|
|
175
|
-
record.payload ??
|
|
176
|
-
{};
|
|
177
|
-
let args = '{}';
|
|
178
|
-
if (typeof argsSource === 'string' && argsSource.trim().length) {
|
|
179
|
-
args = argsSource.trim();
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
try {
|
|
183
|
-
args = JSON.stringify(argsSource ?? {});
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
args = '{}';
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return { name: candidateName, args };
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
19
|
+
function buildGlmResponseCompatInput(payload, adapterContext) {
|
|
20
|
+
return {
|
|
21
|
+
payload,
|
|
22
|
+
adapterContext: buildGlmCompatContext(adapterContext),
|
|
23
|
+
explicitProfile: PROFILE,
|
|
24
|
+
};
|
|
194
25
|
}
|
|
195
|
-
function
|
|
196
|
-
if (!
|
|
26
|
+
export function extractGlmToolMarkup(root, adapterContext) {
|
|
27
|
+
if (!root || typeof root !== "object" || Array.isArray(root)) {
|
|
197
28
|
return;
|
|
198
29
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (!name) {
|
|
205
|
-
const inline = GLM_INLINE_NAME.exec(block);
|
|
206
|
-
if (inline && inline[1]) {
|
|
207
|
-
name = inline[1].trim();
|
|
208
|
-
block = block.slice(inline[0].length);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (!name) {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
const argsRecord = parseTaggedArgBlock(block);
|
|
215
|
-
if (!argsRecord) {
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
matches.push({
|
|
219
|
-
start: exec.index,
|
|
220
|
-
end: exec.index + exec[0].length,
|
|
221
|
-
call: {
|
|
222
|
-
name,
|
|
223
|
-
args: safeStringify(argsRecord)
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
function parseTaggedArgBlock(block) {
|
|
229
|
-
if (!block || typeof block !== 'string') {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
const record = {};
|
|
233
|
-
GLM_TAGGED_BLOCK.lastIndex = 0;
|
|
234
|
-
let exec;
|
|
235
|
-
while ((exec = GLM_TAGGED_BLOCK.exec(block))) {
|
|
236
|
-
const key = typeof exec[1] === 'string' ? exec[1].trim() : '';
|
|
237
|
-
if (!key) {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
const rawValue = typeof exec[2] === 'string' ? exec[2].trim() : '';
|
|
241
|
-
record[key] = coerceTaggedValue(rawValue);
|
|
242
|
-
}
|
|
243
|
-
return Object.keys(record).length ? record : null;
|
|
244
|
-
}
|
|
245
|
-
function coerceTaggedValue(raw) {
|
|
246
|
-
if (!raw) {
|
|
247
|
-
return '';
|
|
248
|
-
}
|
|
249
|
-
const trimmed = raw.trim();
|
|
250
|
-
try {
|
|
251
|
-
return JSON.parse(trimmed);
|
|
252
|
-
}
|
|
253
|
-
catch {
|
|
254
|
-
return trimmed;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function safeStringify(value) {
|
|
258
|
-
try {
|
|
259
|
-
return JSON.stringify(value ?? {});
|
|
260
|
-
}
|
|
261
|
-
catch {
|
|
262
|
-
return '{}';
|
|
263
|
-
}
|
|
30
|
+
const normalized = runRespInboundStage3CompatWithNative(buildGlmResponseCompatInput(root, adapterContext)).payload;
|
|
31
|
+
Object.keys(root).forEach((key) => {
|
|
32
|
+
delete root[key];
|
|
33
|
+
});
|
|
34
|
+
Object.assign(root, normalized);
|
|
264
35
|
}
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
-
/**
|
|
3
|
-
* Some iFlow models reject OpenAI `tools`/`tool_choice` fields with a non-standard 200 error envelope.
|
|
4
|
-
*
|
|
5
|
-
* For such models, we fall back to "text tool calls":
|
|
6
|
-
* - remove top-level `tools` / `tool_choice` so upstream accepts the request
|
|
7
|
-
* - inject a system instruction telling the model to emit `<tool:...>` blocks
|
|
8
|
-
* - response pipeline will harvest those blocks into canonical `tool_calls`
|
|
9
|
-
*/
|
|
10
2
|
export declare function applyIflowToolTextFallback(payload: JsonObject, options?: {
|
|
11
3
|
models?: string[];
|
|
12
4
|
routeId?: string;
|
|
@@ -1,211 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
|
|
2
|
+
import { applyIflowToolTextFallbackWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
|
|
3
|
+
const PROFILE = 'chat:iflow';
|
|
4
|
+
const DEFAULT_PROVIDER_PROTOCOL = 'openai-chat';
|
|
5
|
+
const DEFAULT_ENTRY_ENDPOINT = '/v1/chat/completions';
|
|
6
|
+
function buildIflowCompatContext(adapterContext) {
|
|
7
|
+
const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
|
|
8
|
+
return {
|
|
9
|
+
...nativeContext,
|
|
10
|
+
compatibilityProfile: nativeContext.compatibilityProfile ??
|
|
11
|
+
adapterContext?.compatibilityProfile ??
|
|
12
|
+
PROFILE,
|
|
13
|
+
providerProtocol: nativeContext.providerProtocol ??
|
|
14
|
+
adapterContext?.providerProtocol ??
|
|
15
|
+
DEFAULT_PROVIDER_PROTOCOL,
|
|
16
|
+
entryEndpoint: nativeContext.entryEndpoint ??
|
|
17
|
+
adapterContext?.entryEndpoint ??
|
|
18
|
+
DEFAULT_ENTRY_ENDPOINT,
|
|
19
|
+
};
|
|
4
20
|
}
|
|
5
|
-
function isWebSearchRoute(routeId) {
|
|
6
|
-
if (typeof routeId !== 'string') {
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
const normalized = routeId.trim().toLowerCase();
|
|
10
|
-
return normalized.startsWith('web_search') || normalized.startsWith('search');
|
|
11
|
-
}
|
|
12
|
-
function hasNonEmptyArray(value) {
|
|
13
|
-
return Array.isArray(value) && value.length > 0;
|
|
14
|
-
}
|
|
15
|
-
function ensureSystemMessage(messages) {
|
|
16
|
-
const first = messages[0];
|
|
17
|
-
if (isRecord(first) && typeof first.role === 'string' && first.role.toLowerCase() === 'system') {
|
|
18
|
-
return first;
|
|
19
|
-
}
|
|
20
|
-
const sys = { role: 'system', content: '' };
|
|
21
|
-
messages.unshift(sys);
|
|
22
|
-
return sys;
|
|
23
|
-
}
|
|
24
|
-
function appendSystemText(sys, extra) {
|
|
25
|
-
const prev = typeof sys.content === 'string' ? sys.content : '';
|
|
26
|
-
if (prev.includes('## Tool Calls (Text Markup Mode)')) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const combined = prev ? `${prev}\n\n${extra}` : extra;
|
|
30
|
-
sys.content = combined;
|
|
31
|
-
}
|
|
32
|
-
function buildToolMarkupInstruction() {
|
|
33
|
-
// Keep this concise. The response-side canonicalizer will harvest these blocks into tool_calls.
|
|
34
|
-
return [
|
|
35
|
-
'## Tool Calls (Text Markup Mode)',
|
|
36
|
-
'',
|
|
37
|
-
'You MAY call tools by emitting one or more XML blocks with this exact format, and nothing else inside the block:',
|
|
38
|
-
'',
|
|
39
|
-
'<tool:exec_command>',
|
|
40
|
-
'<command>...</command>',
|
|
41
|
-
'<timeout_ms>10000</timeout_ms>',
|
|
42
|
-
'</tool:exec_command>',
|
|
43
|
-
'',
|
|
44
|
-
'<tool:write_stdin>',
|
|
45
|
-
'<session_id>...</session_id>',
|
|
46
|
-
'<input>...</input>',
|
|
47
|
-
'</tool:write_stdin>',
|
|
48
|
-
'',
|
|
49
|
-
'Rules:',
|
|
50
|
-
'- Use `<tool:exec_command>` to run shell commands.',
|
|
51
|
-
'- Use `<tool:write_stdin>` to send input to an existing session.',
|
|
52
|
-
'- Do NOT wrap these blocks in code fences.',
|
|
53
|
-
'- Do NOT invent tools; only use exec_command and write_stdin.',
|
|
54
|
-
''
|
|
55
|
-
].join('\n');
|
|
56
|
-
}
|
|
57
|
-
function stringifyToolOutput(value) {
|
|
58
|
-
if (typeof value === 'string')
|
|
59
|
-
return value;
|
|
60
|
-
if (value === null || value === undefined)
|
|
61
|
-
return '';
|
|
62
|
-
try {
|
|
63
|
-
return JSON.stringify(value, null, 2);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return String(value);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function coerceToolCallToXmlBlock(toolCall) {
|
|
70
|
-
if (!isRecord(toolCall))
|
|
71
|
-
return null;
|
|
72
|
-
const type = typeof toolCall.type === 'string' ? toolCall.type.trim().toLowerCase() : '';
|
|
73
|
-
if (type && type !== 'function')
|
|
74
|
-
return null;
|
|
75
|
-
const fn = isRecord(toolCall.function) ? toolCall.function : null;
|
|
76
|
-
const name = typeof fn?.name === 'string' ? fn.name.trim() : '';
|
|
77
|
-
if (!name)
|
|
78
|
-
return null;
|
|
79
|
-
const argsRaw = fn?.arguments;
|
|
80
|
-
let args = null;
|
|
81
|
-
if (typeof argsRaw === 'string' && argsRaw.trim().length) {
|
|
82
|
-
try {
|
|
83
|
-
const parsed = JSON.parse(argsRaw);
|
|
84
|
-
args = isRecord(parsed) ? parsed : null;
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
args = { raw: argsRaw };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
else if (isRecord(argsRaw)) {
|
|
91
|
-
args = argsRaw;
|
|
92
|
-
}
|
|
93
|
-
if (name === 'exec_command') {
|
|
94
|
-
const command = typeof args?.cmd === 'string'
|
|
95
|
-
? args.cmd
|
|
96
|
-
: typeof args?.command === 'string'
|
|
97
|
-
? args.command
|
|
98
|
-
: '';
|
|
99
|
-
if (!command)
|
|
100
|
-
return null;
|
|
101
|
-
const timeoutMs = typeof args?.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
|
|
102
|
-
? Math.floor(args.timeout_ms)
|
|
103
|
-
: undefined;
|
|
104
|
-
return [
|
|
105
|
-
'<tool:exec_command>',
|
|
106
|
-
`<command>${command}</command>`,
|
|
107
|
-
...(timeoutMs !== undefined ? [`<timeout_ms>${timeoutMs}</timeout_ms>`] : []),
|
|
108
|
-
'</tool:exec_command>'
|
|
109
|
-
].join('\n');
|
|
110
|
-
}
|
|
111
|
-
if (name === 'write_stdin') {
|
|
112
|
-
const sessionId = typeof args?.session_id === 'string'
|
|
113
|
-
? args.session_id
|
|
114
|
-
: typeof args?.sessionId === 'string'
|
|
115
|
-
? args.sessionId
|
|
116
|
-
: '';
|
|
117
|
-
const input = typeof args?.chars === 'string'
|
|
118
|
-
? args.chars
|
|
119
|
-
: typeof args?.input === 'string'
|
|
120
|
-
? args.input
|
|
121
|
-
: '';
|
|
122
|
-
if (!sessionId || !input)
|
|
123
|
-
return null;
|
|
124
|
-
return [
|
|
125
|
-
'<tool:write_stdin>',
|
|
126
|
-
`<session_id>${sessionId}</session_id>`,
|
|
127
|
-
`<input>${input}</input>`,
|
|
128
|
-
'</tool:write_stdin>'
|
|
129
|
-
].join('\n');
|
|
130
|
-
}
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
function rewriteMessageToolSurfaceInPlace(message) {
|
|
134
|
-
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
135
|
-
// Tool result messages are not supported by some iFlow models; convert them into plain text.
|
|
136
|
-
if (role === 'tool') {
|
|
137
|
-
const toolName = typeof message.name === 'string' ? message.name.trim() : '';
|
|
138
|
-
const toolCallId = typeof message.tool_call_id === 'string' ? String(message.tool_call_id).trim() : '';
|
|
139
|
-
const content = stringifyToolOutput(message.content);
|
|
140
|
-
message.role = 'user';
|
|
141
|
-
message.content = [
|
|
142
|
-
'Tool result:',
|
|
143
|
-
...(toolName ? [`name: ${toolName}`] : []),
|
|
144
|
-
...(toolCallId ? [`tool_call_id: ${toolCallId}`] : []),
|
|
145
|
-
'output:',
|
|
146
|
-
content
|
|
147
|
-
].join('\n');
|
|
148
|
-
delete message.name;
|
|
149
|
-
delete message.tool_call_id;
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
// Assistant tool_calls are not supported by some iFlow models; convert them to XML tool markup.
|
|
153
|
-
const toolCallsRaw = message.tool_calls;
|
|
154
|
-
if (hasNonEmptyArray(toolCallsRaw)) {
|
|
155
|
-
const blocks = toolCallsRaw
|
|
156
|
-
.map(coerceToolCallToXmlBlock)
|
|
157
|
-
.filter((b) => typeof b === 'string' && b.length > 0);
|
|
158
|
-
if (blocks.length) {
|
|
159
|
-
const prev = typeof message.content === 'string' ? message.content : '';
|
|
160
|
-
const joined = blocks.join('\n\n');
|
|
161
|
-
message.content = prev && prev.trim().length ? `${prev}\n\n${joined}` : joined;
|
|
162
|
-
}
|
|
163
|
-
delete message.tool_calls;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Some iFlow models reject OpenAI `tools`/`tool_choice` fields with a non-standard 200 error envelope.
|
|
168
|
-
*
|
|
169
|
-
* For such models, we fall back to "text tool calls":
|
|
170
|
-
* - remove top-level `tools` / `tool_choice` so upstream accepts the request
|
|
171
|
-
* - inject a system instruction telling the model to emit `<tool:...>` blocks
|
|
172
|
-
* - response pipeline will harvest those blocks into canonical `tool_calls`
|
|
173
|
-
*/
|
|
174
21
|
export function applyIflowToolTextFallback(payload, options) {
|
|
175
|
-
if (!payload || typeof payload !== 'object') {
|
|
22
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
176
23
|
return payload;
|
|
177
24
|
}
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return root;
|
|
183
|
-
}
|
|
184
|
-
const model = normalizeModel(root.model);
|
|
185
|
-
const models = Array.isArray(options?.models) ? options.models.map((m) => normalizeModel(m)).filter(Boolean) : [];
|
|
186
|
-
if (!model || !models.includes(model)) {
|
|
187
|
-
return root;
|
|
188
|
-
}
|
|
189
|
-
// Only apply when we have a message array to carry the injected instruction.
|
|
190
|
-
const messages = root.messages;
|
|
191
|
-
if (!hasNonEmptyArray(messages)) {
|
|
192
|
-
return root;
|
|
193
|
-
}
|
|
194
|
-
// Always strip tool schemas for fallback models (including followups),
|
|
195
|
-
// otherwise the first turn might succeed but the tool loop breaks.
|
|
196
|
-
if ('tools' in root) {
|
|
197
|
-
delete root.tools;
|
|
198
|
-
}
|
|
199
|
-
if ('tool_choice' in root) {
|
|
200
|
-
delete root.tool_choice;
|
|
201
|
-
}
|
|
202
|
-
// Rewrite message-level tool protocol to plain text so upstream accepts it.
|
|
203
|
-
for (const msg of messages) {
|
|
204
|
-
if (!isRecord(msg))
|
|
205
|
-
continue;
|
|
206
|
-
rewriteMessageToolSurfaceInPlace(msg);
|
|
207
|
-
}
|
|
208
|
-
const sys = ensureSystemMessage(messages);
|
|
209
|
-
appendSystemText(sys, buildToolMarkupInstruction());
|
|
210
|
-
return root;
|
|
25
|
+
const adapterContext = options?.routeId
|
|
26
|
+
? { routeId: options.routeId }
|
|
27
|
+
: undefined;
|
|
28
|
+
return applyIflowToolTextFallbackWithNative(payload, buildIflowCompatContext(adapterContext), Array.isArray(options?.models) ? options?.models : []);
|
|
211
29
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AdapterContext } from '../../hub/types/chat-envelope.js';
|
|
1
2
|
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
-
export declare function applyQwenRequestTransform(payload: JsonObject): JsonObject;
|
|
3
|
-
export declare function applyQwenResponseTransform(payload: JsonObject): JsonObject;
|
|
3
|
+
export declare function applyQwenRequestTransform(payload: JsonObject, adapterContext?: AdapterContext): JsonObject;
|
|
4
|
+
export declare function applyQwenResponseTransform(payload: JsonObject, adapterContext?: AdapterContext): JsonObject;
|