@jsonstudio/llms 0.6.141 → 0.6.187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/codecs/gemini-openai-codec.js +15 -1
- package/dist/conversion/compat/actions/auto-thinking.d.ts +6 -0
- package/dist/conversion/compat/actions/auto-thinking.js +25 -0
- package/dist/conversion/compat/actions/field-mapping.d.ts +14 -0
- package/dist/conversion/compat/actions/field-mapping.js +155 -0
- package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -0
- package/dist/conversion/compat/actions/qwen-transform.js +209 -0
- package/dist/conversion/compat/actions/request-rules.d.ts +24 -0
- package/dist/conversion/compat/actions/request-rules.js +63 -0
- package/dist/conversion/compat/actions/response-blacklist.d.ts +14 -0
- package/dist/conversion/compat/actions/response-blacklist.js +85 -0
- package/dist/conversion/compat/actions/response-normalize.d.ts +5 -0
- package/dist/conversion/compat/actions/response-normalize.js +121 -0
- package/dist/conversion/compat/actions/response-validate.d.ts +5 -0
- package/dist/conversion/compat/actions/response-validate.js +76 -0
- package/dist/conversion/compat/actions/snapshot.d.ts +8 -0
- package/dist/conversion/compat/actions/snapshot.js +21 -0
- package/dist/conversion/compat/actions/tool-schema.d.ts +6 -0
- package/dist/conversion/compat/actions/tool-schema.js +91 -0
- package/dist/conversion/compat/actions/universal-shape-filter.d.ts +74 -0
- package/dist/conversion/compat/actions/universal-shape-filter.js +382 -0
- package/dist/conversion/compat/profiles/chat-glm.json +187 -13
- package/dist/conversion/compat/profiles/chat-iflow.json +177 -9
- package/dist/conversion/compat/profiles/chat-lmstudio.json +10 -2
- package/dist/conversion/compat/profiles/chat-qwen.json +14 -10
- package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
- package/dist/conversion/hub/pipeline/compat/compat-engine.js +409 -5
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +47 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +35 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
- package/dist/conversion/hub/pipeline/target-utils.js +3 -0
- package/dist/conversion/hub/response/response-runtime.js +19 -2
- package/dist/conversion/responses/responses-host-policy.d.ts +6 -0
- package/dist/conversion/responses/responses-host-policy.js +14 -0
- package/dist/conversion/responses/responses-openai-bridge.js +51 -2
- package/dist/conversion/shared/anthropic-message-utils.js +6 -0
- package/dist/conversion/shared/responses-conversation-store.js +3 -26
- package/dist/conversion/shared/responses-reasoning-registry.d.ts +4 -0
- package/dist/conversion/shared/responses-reasoning-registry.js +62 -1
- package/dist/conversion/shared/responses-response-utils.js +23 -1
- package/dist/conversion/shared/tool-canonicalizer.d.ts +2 -0
- package/dist/conversion/shared/tool-filter-pipeline.js +11 -0
- package/dist/router/virtual-router/bootstrap.js +218 -39
- package/dist/router/virtual-router/classifier.js +19 -52
- package/dist/router/virtual-router/context-advisor.d.ts +21 -0
- package/dist/router/virtual-router/context-advisor.js +76 -0
- package/dist/router/virtual-router/engine.d.ts +11 -26
- package/dist/router/virtual-router/engine.js +191 -386
- package/dist/router/virtual-router/features.js +24 -621
- package/dist/router/virtual-router/health-manager.js +2 -7
- package/dist/router/virtual-router/message-utils.d.ts +7 -0
- package/dist/router/virtual-router/message-utils.js +66 -0
- package/dist/router/virtual-router/provider-registry.js +6 -2
- package/dist/router/virtual-router/token-estimator.d.ts +2 -0
- package/dist/router/virtual-router/token-estimator.js +16 -0
- package/dist/router/virtual-router/tool-signals.d.ts +13 -0
- package/dist/router/virtual-router/tool-signals.js +403 -0
- package/dist/router/virtual-router/types.d.ts +21 -7
- package/dist/router/virtual-router/types.js +1 -0
- package/package.json +2 -2
|
@@ -1,133 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { detectExtendedThinkingKeyword, detectImageAttachment, detectKeyword, extractMessageText, getLatestMessageRole, getLatestUserMessage } from './message-utils.js';
|
|
2
|
+
import { detectCodingTool, detectLastAssistantToolCategory, detectVisionTool, detectWebTool, extractMeaningfulDeclaredToolNames } from './tool-signals.js';
|
|
3
|
+
import { computeRequestTokens } from './token-estimator.js';
|
|
2
4
|
const THINKING_KEYWORDS = ['let me think', 'chain of thought', 'cot', 'reason step', 'deliberate'];
|
|
3
|
-
const WEB_TOOL_KEYWORDS = ['websearch', 'web_search', 'web-search', 'webfetch', 'web_fetch', 'web_request', 'search_web', 'internet_search'];
|
|
4
|
-
const READ_TOOL_EXACT = new Set([
|
|
5
|
-
'read_file',
|
|
6
|
-
'read_text',
|
|
7
|
-
'view_file',
|
|
8
|
-
'view_code',
|
|
9
|
-
'view_document',
|
|
10
|
-
'open_file',
|
|
11
|
-
'get_file',
|
|
12
|
-
'download_file',
|
|
13
|
-
'describe_current_request',
|
|
14
|
-
'list_dir',
|
|
15
|
-
'list_directory',
|
|
16
|
-
'list_files',
|
|
17
|
-
'list_documents',
|
|
18
|
-
'list_resources',
|
|
19
|
-
'search_files',
|
|
20
|
-
'find_files'
|
|
21
|
-
]);
|
|
22
|
-
const WRITE_TOOL_EXACT = new Set([
|
|
23
|
-
'apply_patch',
|
|
24
|
-
'write_file',
|
|
25
|
-
'create_file',
|
|
26
|
-
'modify_file',
|
|
27
|
-
'edit_file',
|
|
28
|
-
'update_file',
|
|
29
|
-
'save_file',
|
|
30
|
-
'append_file',
|
|
31
|
-
'replace_file',
|
|
32
|
-
'delete_file',
|
|
33
|
-
'remove_file',
|
|
34
|
-
'rename_file',
|
|
35
|
-
'move_file',
|
|
36
|
-
'copy_file',
|
|
37
|
-
'mkdir',
|
|
38
|
-
'rmdir'
|
|
39
|
-
]);
|
|
40
|
-
const SEARCH_TOOL_EXACT = new Set(['websearch', 'web_search', 'search_web', 'internet_search', 'webfetch', 'web_fetch']);
|
|
41
|
-
const READ_TOOL_KEYWORDS = ['read', 'list', 'view', 'download', 'open', 'show', 'fetch', 'inspect'];
|
|
42
|
-
const WRITE_TOOL_KEYWORDS = ['write', 'patch', 'modify', 'edit', 'create', 'update', 'append', 'replace', 'delete', 'remove'];
|
|
43
|
-
const SEARCH_TOOL_KEYWORDS = ['search', 'websearch', 'web_fetch', 'webfetch', 'web-request', 'web_request', 'internet'];
|
|
44
|
-
const SHELL_TOOL_NAMES = new Set(['shell_command', 'shell', 'bash']);
|
|
45
|
-
const SHELL_HEREDOC_PATTERN = /<<\s*['"]?[a-z0-9_-]+/i;
|
|
46
|
-
const COMMAND_DETAIL_MAX_LENGTH = 80;
|
|
47
|
-
const TOOL_CATEGORY_PRIORITY = ['search', 'write', 'read', 'other'];
|
|
48
|
-
const SHELL_WRITE_PATTERNS = [
|
|
49
|
-
'apply_patch',
|
|
50
|
-
'sed -i',
|
|
51
|
-
'perl -pi',
|
|
52
|
-
'tee ',
|
|
53
|
-
'cat <<',
|
|
54
|
-
'cat >',
|
|
55
|
-
'printf >',
|
|
56
|
-
'touch ',
|
|
57
|
-
'truncate',
|
|
58
|
-
'mkdir',
|
|
59
|
-
'mktemp',
|
|
60
|
-
'rmdir',
|
|
61
|
-
'rm ',
|
|
62
|
-
'rm-',
|
|
63
|
-
'unlink',
|
|
64
|
-
'mv ',
|
|
65
|
-
'cp ',
|
|
66
|
-
'ln -',
|
|
67
|
-
'chmod',
|
|
68
|
-
'chown',
|
|
69
|
-
'chgrp',
|
|
70
|
-
'tar ',
|
|
71
|
-
'git add',
|
|
72
|
-
'git commit',
|
|
73
|
-
'git apply',
|
|
74
|
-
'git am',
|
|
75
|
-
'git rebase',
|
|
76
|
-
'git checkout',
|
|
77
|
-
'git merge',
|
|
78
|
-
'patch <<',
|
|
79
|
-
'npm install',
|
|
80
|
-
'pnpm install',
|
|
81
|
-
'yarn add',
|
|
82
|
-
'yarn install',
|
|
83
|
-
'pip install',
|
|
84
|
-
'pip3 install',
|
|
85
|
-
'brew install',
|
|
86
|
-
'cargo add',
|
|
87
|
-
'cargo install',
|
|
88
|
-
'go install',
|
|
89
|
-
'make install'
|
|
90
|
-
];
|
|
91
|
-
const SHELL_SEARCH_PATTERNS = [
|
|
92
|
-
'rg ',
|
|
93
|
-
'rg-',
|
|
94
|
-
'grep ',
|
|
95
|
-
'grep-',
|
|
96
|
-
'ripgrep',
|
|
97
|
-
'find ',
|
|
98
|
-
'fd ',
|
|
99
|
-
'locate ',
|
|
100
|
-
'search ',
|
|
101
|
-
'ack ',
|
|
102
|
-
'ag ',
|
|
103
|
-
'where ',
|
|
104
|
-
'which ',
|
|
105
|
-
'codesearch'
|
|
106
|
-
];
|
|
107
|
-
const SHELL_READ_PATTERNS = [
|
|
108
|
-
'ls',
|
|
109
|
-
'dir ',
|
|
110
|
-
'pwd',
|
|
111
|
-
'cat ',
|
|
112
|
-
'type ',
|
|
113
|
-
'head ',
|
|
114
|
-
'tail ',
|
|
115
|
-
'stat',
|
|
116
|
-
'tree',
|
|
117
|
-
'wc ',
|
|
118
|
-
'du ',
|
|
119
|
-
'printf "',
|
|
120
|
-
'python - <<',
|
|
121
|
-
'python -c',
|
|
122
|
-
'node - <<',
|
|
123
|
-
'node -e'
|
|
124
|
-
];
|
|
125
5
|
export function buildRoutingFeatures(request, metadata) {
|
|
126
6
|
const latestUserMessage = getLatestUserMessage(request.messages);
|
|
7
|
+
const latestMessageRole = getLatestMessageRole(request.messages);
|
|
127
8
|
const assistantMessages = request.messages.filter((msg) => msg.role === 'assistant');
|
|
128
9
|
const latestUserText = latestUserMessage ? extractMessageText(latestUserMessage) : '';
|
|
129
10
|
const normalizedUserText = latestUserText.toLowerCase();
|
|
130
|
-
const
|
|
11
|
+
const meaningfulDeclaredTools = extractMeaningfulDeclaredToolNames(request.tools);
|
|
12
|
+
const hasTools = meaningfulDeclaredTools.length > 0;
|
|
131
13
|
const hasToolCallResponses = assistantMessages.some((msg) => Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0);
|
|
132
14
|
const estimatedTokens = computeRequestTokens(request, latestUserText);
|
|
133
15
|
const hasThinking = detectKeyword(normalizedUserText, THINKING_KEYWORDS);
|
|
@@ -136,13 +18,25 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
136
18
|
const hasCodingTool = detectCodingTool(request);
|
|
137
19
|
const hasWebTool = detectWebTool(request);
|
|
138
20
|
const hasThinkingKeyword = hasThinking || detectExtendedThinkingKeyword(normalizedUserText);
|
|
139
|
-
const
|
|
21
|
+
const lastAssistantTool = detectLastAssistantToolCategory(assistantMessages);
|
|
22
|
+
const lastAssistantToolLabel = (() => {
|
|
23
|
+
if (!lastAssistantTool) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
if (lastAssistantTool.commandSnippet && lastAssistantTool.commandSnippet.trim()) {
|
|
27
|
+
return lastAssistantTool.commandSnippet.trim();
|
|
28
|
+
}
|
|
29
|
+
if (lastAssistantTool.name && lastAssistantTool.name.trim()) {
|
|
30
|
+
return lastAssistantTool.name.trim();
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
})();
|
|
140
34
|
return {
|
|
141
35
|
requestId: metadata.requestId,
|
|
142
36
|
model: request.model,
|
|
143
37
|
totalMessages: request.messages?.length ?? 0,
|
|
144
38
|
userTextSample: latestUserText.slice(0, 2000),
|
|
145
|
-
toolCount:
|
|
39
|
+
toolCount: meaningfulDeclaredTools.length,
|
|
146
40
|
hasTools,
|
|
147
41
|
hasToolCallResponses,
|
|
148
42
|
hasVisionTool,
|
|
@@ -151,503 +45,12 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
151
45
|
hasCodingTool,
|
|
152
46
|
hasThinkingKeyword,
|
|
153
47
|
estimatedTokens,
|
|
154
|
-
lastAssistantToolCategory:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
assistantCalledWebSearchTool: assistantToolSummary.usedWebSearchTool,
|
|
48
|
+
lastAssistantToolCategory: lastAssistantTool?.category,
|
|
49
|
+
lastAssistantToolSnippet: lastAssistantTool?.commandSnippet,
|
|
50
|
+
lastAssistantToolLabel,
|
|
51
|
+
latestMessageFromUser: latestMessageRole === 'user',
|
|
159
52
|
metadata: {
|
|
160
53
|
...metadata
|
|
161
54
|
}
|
|
162
55
|
};
|
|
163
56
|
}
|
|
164
|
-
function getLatestUserMessage(messages) {
|
|
165
|
-
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
166
|
-
if (messages[idx]?.role === 'user') {
|
|
167
|
-
return messages[idx];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return undefined;
|
|
171
|
-
}
|
|
172
|
-
function extractMessageText(message) {
|
|
173
|
-
if (typeof message.content === 'string' && message.content.trim()) {
|
|
174
|
-
return message.content;
|
|
175
|
-
}
|
|
176
|
-
return '';
|
|
177
|
-
}
|
|
178
|
-
function detectKeyword(text, keywords) {
|
|
179
|
-
if (!text)
|
|
180
|
-
return false;
|
|
181
|
-
return keywords.some((keyword) => text.includes(keyword.toLowerCase()));
|
|
182
|
-
}
|
|
183
|
-
function detectImageAttachment(message) {
|
|
184
|
-
if (!message)
|
|
185
|
-
return false;
|
|
186
|
-
if (!message.metadata || typeof message.metadata !== 'object') {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
const meta = message.metadata;
|
|
190
|
-
const attachments = (meta.attachments ?? null);
|
|
191
|
-
if (Array.isArray(attachments)) {
|
|
192
|
-
return attachments.some((attachment) => {
|
|
193
|
-
const candidate = attachment;
|
|
194
|
-
const typeValue = typeof candidate.type === 'string' ? candidate.type.toLowerCase() : '';
|
|
195
|
-
const urlValue = typeof candidate.url === 'string'
|
|
196
|
-
? candidate.url
|
|
197
|
-
: typeof candidate.src === 'string'
|
|
198
|
-
? candidate.src
|
|
199
|
-
: typeof candidate.image_url === 'string'
|
|
200
|
-
? candidate.image_url
|
|
201
|
-
: typeof candidate.image_url?.url === 'string'
|
|
202
|
-
? candidate.image_url.url
|
|
203
|
-
: '';
|
|
204
|
-
return typeValue.includes('image') && urlValue.trim().length > 0;
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
if (typeof meta.attachmentType === 'string' && meta.attachmentType.toLowerCase().includes('image')) {
|
|
208
|
-
const urlCandidate = typeof meta.attachmentUrl === 'string' ? meta.attachmentUrl : '';
|
|
209
|
-
return urlCandidate.trim().length > 0;
|
|
210
|
-
}
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
function detectVisionTool(request) {
|
|
214
|
-
if (!Array.isArray(request.tools)) {
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
return request.tools.some((tool) => {
|
|
218
|
-
const functionName = extractToolName(tool);
|
|
219
|
-
const description = extractToolDescription(tool);
|
|
220
|
-
return /vision|image|picture|photo/i.test(functionName) || /vision|image|picture|photo/i.test(description || '');
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
function detectCodingTool(request) {
|
|
224
|
-
if (!Array.isArray(request.tools)) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
return request.tools.some((tool) => {
|
|
228
|
-
const functionName = extractToolName(tool).toLowerCase();
|
|
229
|
-
const description = (extractToolDescription(tool) || '').toLowerCase();
|
|
230
|
-
if (!functionName && !description) {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
if (WRITE_TOOL_EXACT.has(functionName)) {
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
return WRITE_TOOL_KEYWORDS.some((keyword) => functionName.includes(keyword.toLowerCase()) || description.includes(keyword.toLowerCase()));
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
function detectWebTool(request) {
|
|
240
|
-
if (!Array.isArray(request.tools)) {
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
return request.tools.some((tool) => {
|
|
244
|
-
const functionName = extractToolName(tool);
|
|
245
|
-
const description = extractToolDescription(tool);
|
|
246
|
-
const normalizedName = functionName.toLowerCase();
|
|
247
|
-
const normalizedDesc = (description || '').toLowerCase();
|
|
248
|
-
return (WEB_TOOL_KEYWORDS.some((keyword) => normalizedName.includes(keyword)) ||
|
|
249
|
-
WEB_TOOL_KEYWORDS.some((keyword) => normalizedDesc.includes(keyword)));
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
function detectExtendedThinkingKeyword(text) {
|
|
253
|
-
if (!text) {
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
|
-
const keywords = ['仔细分析', '思考', '超级思考', '深度思考', 'careful analysis', 'deep thinking', 'deliberate'];
|
|
257
|
-
return keywords.some((keyword) => text.includes(keyword));
|
|
258
|
-
}
|
|
259
|
-
function computeRequestTokens(request, fallbackText) {
|
|
260
|
-
try {
|
|
261
|
-
return countRequestTokens(request);
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
return fallbackEstimateTokens(fallbackText, request.messages?.length ?? 0);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
function fallbackEstimateTokens(text, messageCount) {
|
|
268
|
-
if (!text) {
|
|
269
|
-
return Math.max(32, Math.max(messageCount, 1) * 16);
|
|
270
|
-
}
|
|
271
|
-
const rough = Math.ceil(text.length / 4);
|
|
272
|
-
return Math.max(rough, Math.max(messageCount, 1) * 32);
|
|
273
|
-
}
|
|
274
|
-
function extractToolName(tool) {
|
|
275
|
-
if (!tool || typeof tool !== 'object') {
|
|
276
|
-
return '';
|
|
277
|
-
}
|
|
278
|
-
const candidate = tool;
|
|
279
|
-
const fromFunction = candidate.function;
|
|
280
|
-
if (fromFunction && typeof fromFunction.name === 'string' && fromFunction.name.trim()) {
|
|
281
|
-
return fromFunction.name;
|
|
282
|
-
}
|
|
283
|
-
if (typeof candidate.name === 'string' && candidate.name.trim()) {
|
|
284
|
-
return candidate.name;
|
|
285
|
-
}
|
|
286
|
-
return '';
|
|
287
|
-
}
|
|
288
|
-
function extractToolDescription(tool) {
|
|
289
|
-
if (!tool || typeof tool !== 'object') {
|
|
290
|
-
return '';
|
|
291
|
-
}
|
|
292
|
-
const candidate = tool;
|
|
293
|
-
const fromFunction = candidate.function;
|
|
294
|
-
if (fromFunction && typeof fromFunction.description === 'string' && fromFunction.description.trim()) {
|
|
295
|
-
return fromFunction.description;
|
|
296
|
-
}
|
|
297
|
-
if (typeof candidate.description === 'string' && candidate.description.trim()) {
|
|
298
|
-
return candidate.description;
|
|
299
|
-
}
|
|
300
|
-
return '';
|
|
301
|
-
}
|
|
302
|
-
function summarizeAssistantToolUsage(messages) {
|
|
303
|
-
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
304
|
-
const msg = messages[idx];
|
|
305
|
-
if (!msg || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0) {
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
const classifications = [];
|
|
309
|
-
let usedWebSearchTool = false;
|
|
310
|
-
for (const call of msg.tool_calls) {
|
|
311
|
-
const classification = classifyToolCall(call);
|
|
312
|
-
if (classification) {
|
|
313
|
-
classifications.push(classification);
|
|
314
|
-
if (classification.category === 'search' && isWebSearchToolInvocation(classification)) {
|
|
315
|
-
usedWebSearchTool = true;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
if (!classifications.length) {
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
const categorySet = new Set();
|
|
323
|
-
for (const classification of classifications) {
|
|
324
|
-
categorySet.add(classification.category);
|
|
325
|
-
}
|
|
326
|
-
const categories = orderToolCategories(Array.from(categorySet));
|
|
327
|
-
const primary = classifications.find((classification) => classification.category !== 'other') ?? classifications[0];
|
|
328
|
-
return {
|
|
329
|
-
categories,
|
|
330
|
-
primary,
|
|
331
|
-
usedWebSearchTool
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
return { categories: [], usedWebSearchTool: false };
|
|
335
|
-
}
|
|
336
|
-
function orderToolCategories(categories) {
|
|
337
|
-
const ordered = [];
|
|
338
|
-
for (const category of TOOL_CATEGORY_PRIORITY) {
|
|
339
|
-
if (categories.includes(category)) {
|
|
340
|
-
ordered.push(category);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
for (const category of categories) {
|
|
344
|
-
if (!ordered.includes(category)) {
|
|
345
|
-
ordered.push(category);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return ordered;
|
|
349
|
-
}
|
|
350
|
-
function isWebSearchToolName(name) {
|
|
351
|
-
const normalized = name.toLowerCase();
|
|
352
|
-
if (SEARCH_TOOL_EXACT.has(normalized)) {
|
|
353
|
-
return true;
|
|
354
|
-
}
|
|
355
|
-
return WEB_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()));
|
|
356
|
-
}
|
|
357
|
-
function isWebSearchToolInvocation(classification) {
|
|
358
|
-
if (!classification) {
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
if (isWebSearchToolName(classification.name)) {
|
|
362
|
-
return true;
|
|
363
|
-
}
|
|
364
|
-
if (classification.detail) {
|
|
365
|
-
const detail = classification.detail.toLowerCase();
|
|
366
|
-
return WEB_TOOL_KEYWORDS.some((keyword) => detail.includes(keyword.toLowerCase()));
|
|
367
|
-
}
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
function classifyToolCall(call) {
|
|
371
|
-
if (!call || typeof call !== 'object') {
|
|
372
|
-
return undefined;
|
|
373
|
-
}
|
|
374
|
-
const functionName = typeof call?.function?.name === 'string' && call.function.name.trim()
|
|
375
|
-
? canonicalizeToolName(call.function.name)
|
|
376
|
-
: '';
|
|
377
|
-
if (!functionName) {
|
|
378
|
-
return undefined;
|
|
379
|
-
}
|
|
380
|
-
const argsObject = parseToolArguments(call?.function?.arguments);
|
|
381
|
-
const commandText = extractCommandText(argsObject).trim();
|
|
382
|
-
const commandDetail = summarizeCommandDetail(commandText);
|
|
383
|
-
const nameCategory = categorizeToolName(functionName);
|
|
384
|
-
if (nameCategory === 'write' || nameCategory === 'read' || nameCategory === 'search') {
|
|
385
|
-
return { category: nameCategory, name: functionName };
|
|
386
|
-
}
|
|
387
|
-
if (SHELL_TOOL_NAMES.has(functionName)) {
|
|
388
|
-
const shellCategory = classifyShellCommand(commandText);
|
|
389
|
-
return {
|
|
390
|
-
category: shellCategory,
|
|
391
|
-
name: functionName,
|
|
392
|
-
detail: commandDetail
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
if (commandText) {
|
|
396
|
-
const derivedCategory = classifyShellCommand(commandText);
|
|
397
|
-
if (derivedCategory !== 'other') {
|
|
398
|
-
return { category: derivedCategory, name: functionName };
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return { category: 'other', name: functionName };
|
|
402
|
-
}
|
|
403
|
-
function canonicalizeToolName(rawName) {
|
|
404
|
-
const trimmed = rawName.trim();
|
|
405
|
-
const markerIndex = trimmed.indexOf('arg_');
|
|
406
|
-
if (markerIndex > 0) {
|
|
407
|
-
return trimmed.slice(0, markerIndex);
|
|
408
|
-
}
|
|
409
|
-
return trimmed;
|
|
410
|
-
}
|
|
411
|
-
function parseToolArguments(rawArguments) {
|
|
412
|
-
if (!rawArguments) {
|
|
413
|
-
return undefined;
|
|
414
|
-
}
|
|
415
|
-
if (typeof rawArguments === 'string') {
|
|
416
|
-
try {
|
|
417
|
-
return JSON.parse(rawArguments);
|
|
418
|
-
}
|
|
419
|
-
catch {
|
|
420
|
-
return rawArguments;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
if (typeof rawArguments === 'object') {
|
|
424
|
-
return rawArguments;
|
|
425
|
-
}
|
|
426
|
-
return undefined;
|
|
427
|
-
}
|
|
428
|
-
function extractCommandText(args) {
|
|
429
|
-
if (!args) {
|
|
430
|
-
return '';
|
|
431
|
-
}
|
|
432
|
-
if (typeof args === 'string') {
|
|
433
|
-
return args;
|
|
434
|
-
}
|
|
435
|
-
if (Array.isArray(args)) {
|
|
436
|
-
const tokens = collectCommandTokens(args);
|
|
437
|
-
if (tokens.length) {
|
|
438
|
-
return tokens.join(' ');
|
|
439
|
-
}
|
|
440
|
-
const derived = extractFirstStringValue(args);
|
|
441
|
-
return derived ?? '';
|
|
442
|
-
}
|
|
443
|
-
if (typeof args === 'object') {
|
|
444
|
-
const record = args;
|
|
445
|
-
const command = record.command;
|
|
446
|
-
const input = record.input;
|
|
447
|
-
const nestedArgs = record.args;
|
|
448
|
-
if (typeof command === 'string') {
|
|
449
|
-
return command;
|
|
450
|
-
}
|
|
451
|
-
if (Array.isArray(command)) {
|
|
452
|
-
const tokens = collectCommandTokens(command);
|
|
453
|
-
if (tokens.length) {
|
|
454
|
-
return tokens.join(' ');
|
|
455
|
-
}
|
|
456
|
-
const derivedCommand = extractFirstStringValue(command);
|
|
457
|
-
if (derivedCommand) {
|
|
458
|
-
return derivedCommand;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (command && typeof command === 'object') {
|
|
462
|
-
const derivedCommand = extractFirstStringValue(command);
|
|
463
|
-
if (derivedCommand) {
|
|
464
|
-
return derivedCommand;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
if (typeof input === 'string') {
|
|
468
|
-
return input;
|
|
469
|
-
}
|
|
470
|
-
if (typeof nestedArgs === 'string') {
|
|
471
|
-
return nestedArgs;
|
|
472
|
-
}
|
|
473
|
-
if (Array.isArray(nestedArgs)) {
|
|
474
|
-
const tokens = collectCommandTokens(nestedArgs);
|
|
475
|
-
if (tokens.length) {
|
|
476
|
-
return tokens.join(' ');
|
|
477
|
-
}
|
|
478
|
-
const derivedArgs = extractFirstStringValue(nestedArgs);
|
|
479
|
-
if (derivedArgs) {
|
|
480
|
-
return derivedArgs;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (nestedArgs && typeof nestedArgs === 'object') {
|
|
484
|
-
const derivedArgs = extractFirstStringValue(nestedArgs);
|
|
485
|
-
if (derivedArgs) {
|
|
486
|
-
return derivedArgs;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
const fallback = extractFirstStringValue(record);
|
|
490
|
-
if (fallback) {
|
|
491
|
-
return fallback;
|
|
492
|
-
}
|
|
493
|
-
try {
|
|
494
|
-
return JSON.stringify(record);
|
|
495
|
-
}
|
|
496
|
-
catch {
|
|
497
|
-
return '';
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
return '';
|
|
501
|
-
}
|
|
502
|
-
function categorizeToolName(name) {
|
|
503
|
-
const normalized = name.toLowerCase();
|
|
504
|
-
if (SEARCH_TOOL_EXACT.has(normalized) ||
|
|
505
|
-
SEARCH_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
506
|
-
return 'search';
|
|
507
|
-
}
|
|
508
|
-
if (READ_TOOL_EXACT.has(normalized) ||
|
|
509
|
-
READ_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
510
|
-
return 'read';
|
|
511
|
-
}
|
|
512
|
-
if (WRITE_TOOL_EXACT.has(normalized) ||
|
|
513
|
-
WRITE_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
514
|
-
return 'write';
|
|
515
|
-
}
|
|
516
|
-
return 'other';
|
|
517
|
-
}
|
|
518
|
-
function classifyShellCommand(command) {
|
|
519
|
-
if (!command) {
|
|
520
|
-
return 'other';
|
|
521
|
-
}
|
|
522
|
-
if (SHELL_HEREDOC_PATTERN.test(command)) {
|
|
523
|
-
return 'write';
|
|
524
|
-
}
|
|
525
|
-
const segments = splitCommandSegments(command).map(stripShellWrapper);
|
|
526
|
-
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_WRITE_PATTERNS))) {
|
|
527
|
-
return 'write';
|
|
528
|
-
}
|
|
529
|
-
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_SEARCH_PATTERNS))) {
|
|
530
|
-
return 'search';
|
|
531
|
-
}
|
|
532
|
-
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_READ_PATTERNS))) {
|
|
533
|
-
return 'read';
|
|
534
|
-
}
|
|
535
|
-
const stripped = stripShellWrapper(command);
|
|
536
|
-
if (matchesAnyPattern(stripped, SHELL_WRITE_PATTERNS)) {
|
|
537
|
-
return 'write';
|
|
538
|
-
}
|
|
539
|
-
if (matchesAnyPattern(stripped, SHELL_SEARCH_PATTERNS)) {
|
|
540
|
-
return 'search';
|
|
541
|
-
}
|
|
542
|
-
if (matchesAnyPattern(stripped, SHELL_READ_PATTERNS)) {
|
|
543
|
-
return 'read';
|
|
544
|
-
}
|
|
545
|
-
return 'other';
|
|
546
|
-
}
|
|
547
|
-
function splitCommandSegments(command) {
|
|
548
|
-
return command
|
|
549
|
-
.split(/(?:\r?\n|&&|\|\||;)/)
|
|
550
|
-
.map((segment) => segment.trim())
|
|
551
|
-
.filter(Boolean);
|
|
552
|
-
}
|
|
553
|
-
function matchesAnyPattern(text, patterns) {
|
|
554
|
-
if (!text) {
|
|
555
|
-
return false;
|
|
556
|
-
}
|
|
557
|
-
const trimmed = text.trim().toLowerCase();
|
|
558
|
-
const normalized = trimmed.startsWith('sudo ') ? trimmed.slice(5).trim() : trimmed;
|
|
559
|
-
return patterns.some((pattern) => {
|
|
560
|
-
const lowered = pattern.toLowerCase().trim();
|
|
561
|
-
return normalized.startsWith(lowered);
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
function stripShellWrapper(segment) {
|
|
565
|
-
let normalized = segment.trim();
|
|
566
|
-
const wrappers = ['bash -lc ', 'bash -lc', 'sh -c ', 'sh -c', '/bin/sh -c ', '/bin/sh -c', 'env -i bash -lc ', 'env -i bash -lc'];
|
|
567
|
-
for (const wrapper of wrappers) {
|
|
568
|
-
if (normalized.toLowerCase().startsWith(wrapper)) {
|
|
569
|
-
normalized = normalized.slice(wrapper.length).trim();
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
normalized = trimEnclosingQuotes(normalized);
|
|
574
|
-
if (normalized.startsWith('sudo ')) {
|
|
575
|
-
normalized = normalized.slice(5).trim();
|
|
576
|
-
}
|
|
577
|
-
return normalized;
|
|
578
|
-
}
|
|
579
|
-
function trimEnclosingQuotes(value) {
|
|
580
|
-
if ((value.startsWith('"') && value.endsWith('"') && value.length > 1) ||
|
|
581
|
-
(value.startsWith("'") && value.endsWith("'") && value.length > 1)) {
|
|
582
|
-
return value.slice(1, -1).trim();
|
|
583
|
-
}
|
|
584
|
-
return value;
|
|
585
|
-
}
|
|
586
|
-
function summarizeCommandDetail(command) {
|
|
587
|
-
if (!command) {
|
|
588
|
-
return undefined;
|
|
589
|
-
}
|
|
590
|
-
const [firstSegment] = splitCommandSegments(command);
|
|
591
|
-
const candidate = firstSegment ?? command;
|
|
592
|
-
const normalized = candidate.replace(/\s+/g, ' ').trim();
|
|
593
|
-
if (!normalized) {
|
|
594
|
-
return undefined;
|
|
595
|
-
}
|
|
596
|
-
if (normalized.length > COMMAND_DETAIL_MAX_LENGTH) {
|
|
597
|
-
return `${normalized.slice(0, COMMAND_DETAIL_MAX_LENGTH - 1)}…`;
|
|
598
|
-
}
|
|
599
|
-
return normalized;
|
|
600
|
-
}
|
|
601
|
-
function collectCommandTokens(values) {
|
|
602
|
-
const tokens = [];
|
|
603
|
-
for (const value of values) {
|
|
604
|
-
if (typeof value === 'string') {
|
|
605
|
-
const trimmed = value.trim();
|
|
606
|
-
if (trimmed) {
|
|
607
|
-
tokens.push(trimmed);
|
|
608
|
-
}
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
if (Array.isArray(value)) {
|
|
612
|
-
const nested = collectCommandTokens(value);
|
|
613
|
-
if (nested.length) {
|
|
614
|
-
tokens.push(nested.join(' '));
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
if (value && typeof value === 'object') {
|
|
619
|
-
const extracted = extractFirstStringValue(value);
|
|
620
|
-
if (extracted) {
|
|
621
|
-
tokens.push(extracted.trim());
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
return tokens.filter(Boolean).slice(0, 16);
|
|
626
|
-
}
|
|
627
|
-
function extractFirstStringValue(value) {
|
|
628
|
-
if (!value) {
|
|
629
|
-
return undefined;
|
|
630
|
-
}
|
|
631
|
-
if (typeof value === 'string') {
|
|
632
|
-
const trimmed = value.trim();
|
|
633
|
-
return trimmed || undefined;
|
|
634
|
-
}
|
|
635
|
-
if (Array.isArray(value)) {
|
|
636
|
-
for (const item of value) {
|
|
637
|
-
const extracted = extractFirstStringValue(item);
|
|
638
|
-
if (extracted) {
|
|
639
|
-
return extracted;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
return undefined;
|
|
643
|
-
}
|
|
644
|
-
if (typeof value === 'object') {
|
|
645
|
-
for (const candidate of Object.values(value)) {
|
|
646
|
-
const extracted = extractFirstStringValue(candidate);
|
|
647
|
-
if (extracted) {
|
|
648
|
-
return extracted;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return undefined;
|
|
653
|
-
}
|
|
@@ -35,13 +35,8 @@ export class ProviderHealthManager {
|
|
|
35
35
|
if (reason) {
|
|
36
36
|
state.reason = reason;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
state.cooldownExpiresAt = Date.now() + this.config.cooldownMs;
|
|
41
|
-
if (reason) {
|
|
42
|
-
state.reason = reason;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
38
|
+
// 在新版策略中,普通失败仅用于监控;不再根据 failureThreshold 自动熔断。
|
|
39
|
+
// 只有显式 fatal 或 tripProvider 调用时才会进入 tripped 状态。
|
|
45
40
|
return state;
|
|
46
41
|
}
|
|
47
42
|
recordSuccess(providerKey) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StandardizedMessage } from '../../conversion/hub/types/standardized.js';
|
|
2
|
+
export declare function getLatestUserMessage(messages: StandardizedMessage[]): StandardizedMessage | undefined;
|
|
3
|
+
export declare function getLatestMessageRole(messages: StandardizedMessage[]): string | undefined;
|
|
4
|
+
export declare function extractMessageText(message: StandardizedMessage): string;
|
|
5
|
+
export declare function detectKeyword(text: string, keywords: string[]): boolean;
|
|
6
|
+
export declare function detectExtendedThinkingKeyword(text: string): boolean;
|
|
7
|
+
export declare function detectImageAttachment(message: StandardizedMessage | undefined): boolean;
|