@jsonstudio/llms 0.6.567 → 0.6.586
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 +33 -4
- package/dist/conversion/codecs/openai-openai-codec.js +2 -1
- package/dist/conversion/codecs/responses-openai-codec.js +3 -2
- package/dist/conversion/compat/actions/glm-history-image-trim.d.ts +2 -0
- package/dist/conversion/compat/actions/glm-history-image-trim.js +88 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +6 -2
- package/dist/conversion/hub/pipeline/hub-pipeline.js +72 -81
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +0 -34
- package/dist/conversion/hub/process/chat-process.js +68 -24
- package/dist/conversion/hub/response/provider-response.js +0 -8
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +22 -3
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +267 -14
- package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -2
- package/dist/conversion/responses/responses-openai-bridge.js +1 -13
- package/dist/conversion/shared/anthropic-message-utils.js +54 -0
- package/dist/conversion/shared/args-mapping.js +11 -3
- package/dist/conversion/shared/responses-output-builder.js +42 -21
- package/dist/conversion/shared/streaming-text-extractor.d.ts +25 -0
- package/dist/conversion/shared/streaming-text-extractor.js +31 -38
- package/dist/conversion/shared/text-markup-normalizer.d.ts +20 -0
- package/dist/conversion/shared/text-markup-normalizer.js +118 -31
- package/dist/conversion/shared/tool-filter-pipeline.js +56 -30
- package/dist/conversion/shared/tool-harvester.js +43 -12
- package/dist/conversion/shared/tool-mapping.d.ts +1 -0
- package/dist/conversion/shared/tool-mapping.js +33 -19
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.js +1 -0
- package/dist/filters/special/request-tools-normalize.js +14 -4
- package/dist/filters/special/response-apply-patch-toon-decode.d.ts +23 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +117 -0
- package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +10 -0
- package/dist/filters/special/response-tool-arguments-toon-decode.js +154 -26
- package/dist/guidance/index.js +71 -42
- package/dist/router/virtual-router/bootstrap.js +10 -5
- package/dist/router/virtual-router/classifier.js +16 -7
- package/dist/router/virtual-router/engine-health.d.ts +11 -0
- package/dist/router/virtual-router/engine-health.js +217 -4
- package/dist/router/virtual-router/engine-logging.d.ts +2 -1
- package/dist/router/virtual-router/engine-logging.js +35 -3
- package/dist/router/virtual-router/engine.d.ts +17 -1
- package/dist/router/virtual-router/engine.js +184 -6
- package/dist/router/virtual-router/routing-instructions.d.ts +2 -0
- package/dist/router/virtual-router/routing-instructions.js +19 -1
- package/dist/router/virtual-router/tool-signals.d.ts +2 -1
- package/dist/router/virtual-router/tool-signals.js +324 -119
- package/dist/router/virtual-router/types.d.ts +31 -1
- package/dist/router/virtual-router/types.js +2 -2
- package/dist/servertool/engine.js +3 -0
- package/dist/servertool/handlers/iflow-model-error-retry.d.ts +1 -0
- package/dist/servertool/handlers/iflow-model-error-retry.js +93 -0
- package/dist/servertool/handlers/stop-message-auto.js +61 -4
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +27 -0
- package/dist/sse/json-to-sse/event-generators/responses.js +9 -2
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +23 -3
- package/dist/tools/apply-patch-structured.d.ts +20 -0
- package/dist/tools/apply-patch-structured.js +240 -0
- package/dist/tools/tool-description-utils.d.ts +5 -0
- package/dist/tools/tool-description-utils.js +50 -0
- package/dist/tools/tool-registry.js +11 -193
- package/package.json +1 -1
|
@@ -8,14 +8,7 @@ const READ_TOOL_EXACT = new Set([
|
|
|
8
8
|
'open_file',
|
|
9
9
|
'get_file',
|
|
10
10
|
'download_file',
|
|
11
|
-
'describe_current_request'
|
|
12
|
-
'list_dir',
|
|
13
|
-
'list_directory',
|
|
14
|
-
'list_files',
|
|
15
|
-
'list_documents',
|
|
16
|
-
'list_resources',
|
|
17
|
-
'search_files',
|
|
18
|
-
'find_files'
|
|
11
|
+
'describe_current_request'
|
|
19
12
|
]);
|
|
20
13
|
const WRITE_TOOL_EXACT = new Set([
|
|
21
14
|
'apply_patch',
|
|
@@ -26,83 +19,61 @@ const WRITE_TOOL_EXACT = new Set([
|
|
|
26
19
|
'update_file',
|
|
27
20
|
'save_file',
|
|
28
21
|
'append_file',
|
|
29
|
-
'replace_file'
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
22
|
+
'replace_file'
|
|
23
|
+
]);
|
|
24
|
+
const SEARCH_TOOL_EXACT = new Set([
|
|
25
|
+
'search_files',
|
|
26
|
+
'find_files',
|
|
27
|
+
'search_documents',
|
|
28
|
+
'search_repo',
|
|
29
|
+
'glob_search',
|
|
30
|
+
'grep_files',
|
|
31
|
+
'code_search',
|
|
32
|
+
'lookup_symbol'
|
|
37
33
|
]);
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const SEARCH_TOOL_KEYWORDS = ['search', 'websearch', 'web_fetch', 'webfetch', 'web-request', 'web_request', 'internet'];
|
|
34
|
+
const READ_TOOL_KEYWORDS = ['read', 'view', 'download', 'open', 'show', 'fetch', 'inspect'];
|
|
35
|
+
const WRITE_TOOL_KEYWORDS = ['write', 'patch', 'modify', 'edit', 'create', 'update', 'append', 'replace', 'save'];
|
|
36
|
+
const SEARCH_TOOL_KEYWORDS = ['find', 'grep', 'glob', 'lookup', 'locate'];
|
|
42
37
|
const SHELL_TOOL_NAMES = new Set(['shell_command', 'shell', 'bash']);
|
|
43
38
|
const DECLARED_TOOL_IGNORE = new Set(['exec_command']);
|
|
44
39
|
const SHELL_HEREDOC_PATTERN = /<<\s*['"]?[a-z0-9_-]+/i;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
'brew install',
|
|
83
|
-
'cargo add',
|
|
84
|
-
'cargo install',
|
|
85
|
-
'go install',
|
|
86
|
-
'make install'
|
|
87
|
-
];
|
|
88
|
-
const SHELL_READ_PATTERNS = [
|
|
89
|
-
'ls',
|
|
90
|
-
'dir ',
|
|
91
|
-
'pwd',
|
|
92
|
-
'cat ',
|
|
93
|
-
'type ',
|
|
94
|
-
'head ',
|
|
95
|
-
'tail ',
|
|
96
|
-
'stat',
|
|
97
|
-
'tree',
|
|
98
|
-
'wc ',
|
|
99
|
-
'du ',
|
|
100
|
-
'printf "',
|
|
101
|
-
'python - <<',
|
|
102
|
-
'python -c',
|
|
103
|
-
'node - <<',
|
|
104
|
-
'node -e'
|
|
105
|
-
];
|
|
40
|
+
const SHELL_WRITE_COMMANDS = new Set(['apply_patch', 'tee', 'touch', 'truncate', 'patch']);
|
|
41
|
+
const SHELL_READ_COMMANDS = new Set(['cat', 'head', 'tail', 'awk', 'strings', 'less', 'more', 'nl']);
|
|
42
|
+
const SHELL_SEARCH_COMMANDS = new Set([
|
|
43
|
+
'rg',
|
|
44
|
+
'ripgrep',
|
|
45
|
+
'grep',
|
|
46
|
+
'egrep',
|
|
47
|
+
'fgrep',
|
|
48
|
+
'ag',
|
|
49
|
+
'ack',
|
|
50
|
+
'find',
|
|
51
|
+
'fd',
|
|
52
|
+
'locate',
|
|
53
|
+
'codesearch'
|
|
54
|
+
]);
|
|
55
|
+
const SHELL_REDIRECT_WRITE_BINARIES = new Set(['cat', 'printf', 'python', 'node', 'perl', 'ruby', 'php', 'bash', 'sh', 'zsh', 'echo']);
|
|
56
|
+
const SHELL_WRAPPER_COMMANDS = new Set(['sudo', 'env', 'time', 'nice', 'nohup', 'command', 'stdbuf']);
|
|
57
|
+
const COMMAND_ALIASES = new Map([
|
|
58
|
+
['python3', 'python'],
|
|
59
|
+
['pip3', 'pip'],
|
|
60
|
+
['ripgrep', 'rg'],
|
|
61
|
+
['perl5', 'perl']
|
|
62
|
+
]);
|
|
63
|
+
const GIT_WRITE_SUBCOMMANDS = new Set(['add', 'commit', 'apply', 'am', 'rebase', 'checkout', 'merge']);
|
|
64
|
+
const PACKAGE_MANAGER_COMMANDS = new Map([
|
|
65
|
+
['npm', new Set(['install'])],
|
|
66
|
+
['pnpm', new Set(['install'])],
|
|
67
|
+
['yarn', new Set(['add', 'install'])],
|
|
68
|
+
['pip', new Set(['install'])],
|
|
69
|
+
['pip3', new Set(['install'])],
|
|
70
|
+
['brew', new Set(['install'])],
|
|
71
|
+
['cargo', new Set(['add', 'install'])],
|
|
72
|
+
['go', new Set(['install'])],
|
|
73
|
+
['make', new Set(['install'])]
|
|
74
|
+
]);
|
|
75
|
+
const ENV_ASSIGNMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=.*/;
|
|
76
|
+
const OUTPUT_REDIRECT_PATTERN = /(?:^|[\s;|&])>>?\s*(?!&)[^\s]+/;
|
|
106
77
|
export function detectVisionTool(request) {
|
|
107
78
|
if (!Array.isArray(request.tools)) {
|
|
108
79
|
return false;
|
|
@@ -160,31 +131,46 @@ export function extractMeaningfulDeclaredToolNames(tools) {
|
|
|
160
131
|
}
|
|
161
132
|
return names;
|
|
162
133
|
}
|
|
134
|
+
const TOOL_CATEGORY_PRIORITY = {
|
|
135
|
+
websearch: 4,
|
|
136
|
+
read: 3,
|
|
137
|
+
write: 2,
|
|
138
|
+
search: 1,
|
|
139
|
+
other: 0
|
|
140
|
+
};
|
|
163
141
|
export function detectLastAssistantToolCategory(messages) {
|
|
164
142
|
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
165
143
|
const msg = messages[idx];
|
|
166
144
|
if (!msg || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0) {
|
|
167
145
|
continue;
|
|
168
146
|
}
|
|
169
|
-
|
|
147
|
+
const candidates = [];
|
|
170
148
|
for (const call of msg.tool_calls) {
|
|
171
149
|
const classification = classifyToolCall(call);
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
if (!fallback) {
|
|
176
|
-
fallback = classification;
|
|
177
|
-
}
|
|
178
|
-
if (classification.category !== 'other') {
|
|
179
|
-
return classification;
|
|
150
|
+
if (classification) {
|
|
151
|
+
candidates.push(classification);
|
|
180
152
|
}
|
|
181
153
|
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
154
|
+
if (!candidates.length) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
let best = candidates[0];
|
|
158
|
+
let bestScore = TOOL_CATEGORY_PRIORITY[best.category] ?? 0;
|
|
159
|
+
for (let i = 1; i < candidates.length; i += 1) {
|
|
160
|
+
const candidate = candidates[i];
|
|
161
|
+
const score = TOOL_CATEGORY_PRIORITY[candidate.category] ?? 0;
|
|
162
|
+
if (score > bestScore) {
|
|
163
|
+
best = candidate;
|
|
164
|
+
bestScore = score;
|
|
165
|
+
}
|
|
184
166
|
}
|
|
167
|
+
return best;
|
|
185
168
|
}
|
|
186
169
|
return undefined;
|
|
187
170
|
}
|
|
171
|
+
export function classifyToolCallForReport(call) {
|
|
172
|
+
return classifyToolCall(call);
|
|
173
|
+
}
|
|
188
174
|
function classifyToolCall(call) {
|
|
189
175
|
if (!call || typeof call !== 'object') {
|
|
190
176
|
return undefined;
|
|
@@ -197,21 +183,48 @@ function classifyToolCall(call) {
|
|
|
197
183
|
}
|
|
198
184
|
const argsObject = parseToolArguments(call?.function?.arguments);
|
|
199
185
|
const commandText = extractCommandText(argsObject);
|
|
200
|
-
const nameCategory = categorizeToolName(functionName);
|
|
201
186
|
const snippet = buildCommandSnippet(commandText);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
187
|
+
const normalizedName = functionName.toLowerCase();
|
|
188
|
+
const normalizedCmd = commandText.toLowerCase();
|
|
189
|
+
// 1) Web search 优先:函数名命中 web 搜索关键字时,一律归类为 websearch,优先级最高。
|
|
190
|
+
const isWebSearch = WEB_TOOL_KEYWORDS.some((keyword) => normalizedName.includes(keyword));
|
|
191
|
+
// 2) 基于工具名的初步分类(read / write / search / other)
|
|
192
|
+
const nameCategory = categorizeToolName(functionName);
|
|
193
|
+
// 3) shell_command / exec_command 根据内部命令判断读写性质
|
|
194
|
+
let shellCategory = 'other';
|
|
195
|
+
if (SHELL_TOOL_NAMES.has(functionName) || functionName === 'exec_command') {
|
|
196
|
+
shellCategory = classifyShellCommand(commandText);
|
|
197
|
+
}
|
|
198
|
+
// 按优先级合并分类结果:
|
|
199
|
+
// 1. web search
|
|
200
|
+
// 2. 写文件(任一维度命中写)
|
|
201
|
+
// 3. 读文件(任一维度命中读)
|
|
202
|
+
// 4. 其他搜索(非 web search)
|
|
203
|
+
// 5. 其它工具
|
|
204
|
+
// Priority 1: Web search
|
|
205
|
+
if (isWebSearch) {
|
|
206
|
+
return { category: 'websearch', name: functionName, commandSnippet: snippet };
|
|
207
|
+
}
|
|
208
|
+
// Priority 2: Write (写文件) — 名称或内部命令任一判断为写,都按写处理
|
|
209
|
+
if (nameCategory === 'write' || shellCategory === 'write') {
|
|
210
|
+
return { category: 'write', name: functionName, commandSnippet: snippet };
|
|
211
|
+
}
|
|
212
|
+
// Priority 3: Read (读文件) — 仅在没有写的情况下,再看读
|
|
213
|
+
if (nameCategory === 'read' || shellCategory === 'read') {
|
|
214
|
+
return { category: 'read', name: functionName, commandSnippet: snippet };
|
|
215
|
+
}
|
|
216
|
+
// Priority 4: 其他 search 类工具(非 web search)
|
|
217
|
+
if (nameCategory === 'search' || shellCategory === 'search') {
|
|
218
|
+
return { category: 'search', name: functionName, commandSnippet: snippet };
|
|
219
|
+
}
|
|
220
|
+
// Priority 5: 兜底用命令文本再判断一次 shell 风格读写/搜索(非 shell/exec_command 的工具)
|
|
221
|
+
if (!SHELL_TOOL_NAMES.has(functionName) && functionName !== 'exec_command' && commandText) {
|
|
210
222
|
const derivedCategory = classifyShellCommand(commandText);
|
|
211
|
-
if (derivedCategory
|
|
223
|
+
if (derivedCategory === 'write' || derivedCategory === 'read' || derivedCategory === 'search') {
|
|
212
224
|
return { category: derivedCategory, name: functionName, commandSnippet: snippet };
|
|
213
225
|
}
|
|
214
226
|
}
|
|
227
|
+
// 最终兜底:other
|
|
215
228
|
return { category: 'other', name: functionName, commandSnippet: snippet };
|
|
216
229
|
}
|
|
217
230
|
function extractToolName(tool) {
|
|
@@ -325,12 +338,10 @@ function categorizeToolName(name) {
|
|
|
325
338
|
SEARCH_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
326
339
|
return 'search';
|
|
327
340
|
}
|
|
328
|
-
if (READ_TOOL_EXACT.has(normalized)
|
|
329
|
-
READ_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
341
|
+
if (READ_TOOL_EXACT.has(normalized)) {
|
|
330
342
|
return 'read';
|
|
331
343
|
}
|
|
332
|
-
if (WRITE_TOOL_EXACT.has(normalized)
|
|
333
|
-
WRITE_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
344
|
+
if (WRITE_TOOL_EXACT.has(normalized)) {
|
|
334
345
|
return 'write';
|
|
335
346
|
}
|
|
336
347
|
return 'other';
|
|
@@ -342,28 +353,225 @@ function classifyShellCommand(command) {
|
|
|
342
353
|
if (SHELL_HEREDOC_PATTERN.test(command)) {
|
|
343
354
|
return 'write';
|
|
344
355
|
}
|
|
345
|
-
const segments = splitCommandSegments(command)
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
const segments = splitCommandSegments(command);
|
|
357
|
+
let sawRead = false;
|
|
358
|
+
let sawSearch = false;
|
|
359
|
+
for (const segment of segments) {
|
|
360
|
+
const normalized = normalizeShellSegment(segment);
|
|
361
|
+
if (!normalized) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
for (const args of normalized.commands) {
|
|
365
|
+
if (!args.length) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const [binary, ...rest] = args;
|
|
369
|
+
const normalizedBinary = normalizeBinaryName(binary);
|
|
370
|
+
const alias = COMMAND_ALIASES.get(normalizedBinary) || normalizedBinary;
|
|
371
|
+
if (isWriteBinary(alias, rest, normalized.raw)) {
|
|
372
|
+
return 'write';
|
|
373
|
+
}
|
|
374
|
+
if (isReadBinary(alias, rest)) {
|
|
375
|
+
sawRead = true;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (isSearchBinary(alias, rest)) {
|
|
379
|
+
sawSearch = true;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
348
382
|
}
|
|
349
|
-
if (
|
|
383
|
+
if (sawRead) {
|
|
350
384
|
return 'read';
|
|
351
385
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return 'write';
|
|
355
|
-
}
|
|
356
|
-
if (matchesAnyPattern(stripped, SHELL_READ_PATTERNS)) {
|
|
357
|
-
return 'read';
|
|
386
|
+
if (sawSearch) {
|
|
387
|
+
return 'search';
|
|
358
388
|
}
|
|
359
389
|
return 'other';
|
|
360
390
|
}
|
|
391
|
+
function normalizeShellSegment(segment) {
|
|
392
|
+
const trimmed = stripShellWrapper(segment);
|
|
393
|
+
if (!trimmed) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
const tokens = splitShellTokens(trimmed);
|
|
397
|
+
if (!tokens.length) {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
const commands = [];
|
|
401
|
+
let current = [];
|
|
402
|
+
for (const token of tokens) {
|
|
403
|
+
if (token === '|') {
|
|
404
|
+
const cleaned = cleanCommandTokens(current);
|
|
405
|
+
if (cleaned.length) {
|
|
406
|
+
commands.push(cleaned);
|
|
407
|
+
}
|
|
408
|
+
current = [];
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
current.push(token);
|
|
412
|
+
}
|
|
413
|
+
const cleaned = cleanCommandTokens(current);
|
|
414
|
+
if (cleaned.length) {
|
|
415
|
+
commands.push(cleaned);
|
|
416
|
+
}
|
|
417
|
+
if (!commands.length) {
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
return { raw: trimmed, commands };
|
|
421
|
+
}
|
|
422
|
+
function splitShellTokens(cmd) {
|
|
423
|
+
const tokens = [];
|
|
424
|
+
let current = '';
|
|
425
|
+
let quote = null;
|
|
426
|
+
for (let i = 0; i < cmd.length; i += 1) {
|
|
427
|
+
const ch = cmd[i];
|
|
428
|
+
if (quote) {
|
|
429
|
+
if (ch === quote) {
|
|
430
|
+
quote = null;
|
|
431
|
+
}
|
|
432
|
+
else if (ch === '\\' && quote === '"' && i + 1 < cmd.length) {
|
|
433
|
+
current += cmd[i + 1];
|
|
434
|
+
i += 1;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
current += ch;
|
|
438
|
+
}
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (ch === '"' || ch === "'") {
|
|
442
|
+
quote = ch;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (/[\s\t]/.test(ch)) {
|
|
446
|
+
if (current) {
|
|
447
|
+
tokens.push(current);
|
|
448
|
+
current = '';
|
|
449
|
+
}
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (ch === '|') {
|
|
453
|
+
if (current) {
|
|
454
|
+
tokens.push(current);
|
|
455
|
+
current = '';
|
|
456
|
+
}
|
|
457
|
+
tokens.push('|');
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if ((ch === '|' || ch === '&') && i + 1 < cmd.length && cmd[i + 1] === ch) {
|
|
461
|
+
if (current) {
|
|
462
|
+
tokens.push(current);
|
|
463
|
+
current = '';
|
|
464
|
+
}
|
|
465
|
+
tokens.push(cmd.slice(i, i + 2));
|
|
466
|
+
i += 1;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
current += ch;
|
|
470
|
+
}
|
|
471
|
+
if (current) {
|
|
472
|
+
tokens.push(current);
|
|
473
|
+
}
|
|
474
|
+
return tokens;
|
|
475
|
+
}
|
|
476
|
+
function cleanCommandTokens(tokens) {
|
|
477
|
+
if (!tokens.length) {
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
const cleaned = [];
|
|
481
|
+
for (const token of tokens) {
|
|
482
|
+
if (!cleaned.length) {
|
|
483
|
+
if (ENV_ASSIGNMENT_PATTERN.test(token)) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (SHELL_WRAPPER_COMMANDS.has(token)) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
cleaned.push(token);
|
|
491
|
+
}
|
|
492
|
+
return cleaned;
|
|
493
|
+
}
|
|
494
|
+
function isWriteBinary(binary, args, rawSegment) {
|
|
495
|
+
const normalized = binary.toLowerCase();
|
|
496
|
+
if (SHELL_WRITE_COMMANDS.has(normalized)) {
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
if (normalized === 'git' && args.length > 0) {
|
|
500
|
+
const sub = args[0].toLowerCase();
|
|
501
|
+
if (GIT_WRITE_SUBCOMMANDS.has(sub)) {
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (PACKAGE_MANAGER_COMMANDS.has(normalized)) {
|
|
506
|
+
const allowed = PACKAGE_MANAGER_COMMANDS.get(normalized);
|
|
507
|
+
if (args.length > 0 && allowed.has(args[0].toLowerCase())) {
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (normalized === 'sed') {
|
|
512
|
+
const joined = args.join(' ').toLowerCase();
|
|
513
|
+
if (joined.includes('-i')) {
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (normalized === 'perl') {
|
|
518
|
+
const joined = args.join(' ').toLowerCase();
|
|
519
|
+
if (joined.includes('-pi')) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (normalized === 'printf' && OUTPUT_REDIRECT_PATTERN.test(rawSegment)) {
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
if (SHELL_REDIRECT_WRITE_BINARIES.has(normalized) && OUTPUT_REDIRECT_PATTERN.test(rawSegment)) {
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
function isReadBinary(binary, args) {
|
|
532
|
+
const normalized = binary.toLowerCase();
|
|
533
|
+
if (SHELL_READ_COMMANDS.has(normalized)) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
if (normalized === 'sed') {
|
|
537
|
+
const joined = args.join(' ').toLowerCase();
|
|
538
|
+
if (joined.includes('-i')) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
function isSearchBinary(binary, args) {
|
|
546
|
+
const normalized = binary.toLowerCase();
|
|
547
|
+
if (SHELL_SEARCH_COMMANDS.has(normalized)) {
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
if (normalized === 'git' && args.length > 0) {
|
|
551
|
+
const sub = args[0].toLowerCase();
|
|
552
|
+
if (sub === 'grep') {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
361
558
|
function splitCommandSegments(command) {
|
|
362
559
|
return command
|
|
363
560
|
.split(/(?:\r?\n|&&|\|\||;)/)
|
|
364
561
|
.map((segment) => segment.trim())
|
|
365
562
|
.filter(Boolean);
|
|
366
563
|
}
|
|
564
|
+
function normalizeBinaryName(binary) {
|
|
565
|
+
if (!binary) {
|
|
566
|
+
return '';
|
|
567
|
+
}
|
|
568
|
+
const lowered = binary.toLowerCase();
|
|
569
|
+
const slashIndex = lowered.lastIndexOf('/');
|
|
570
|
+
if (slashIndex >= 0) {
|
|
571
|
+
return lowered.slice(slashIndex + 1);
|
|
572
|
+
}
|
|
573
|
+
return lowered;
|
|
574
|
+
}
|
|
367
575
|
function stripShellWrapper(command) {
|
|
368
576
|
if (!command) {
|
|
369
577
|
return '';
|
|
@@ -376,6 +584,3 @@ function stripShellWrapper(command) {
|
|
|
376
584
|
}
|
|
377
585
|
return command.trim();
|
|
378
586
|
}
|
|
379
|
-
function matchesAnyPattern(command, patterns) {
|
|
380
|
-
return patterns.some((pattern) => command.includes(pattern));
|
|
381
|
-
}
|
|
@@ -172,6 +172,11 @@ export interface RouterMetadataInput {
|
|
|
172
172
|
* 强制路由模式,从消息中的 <**...**> 指令解析得出
|
|
173
173
|
*/
|
|
174
174
|
routingMode?: RoutingInstructionMode;
|
|
175
|
+
/**
|
|
176
|
+
* 当 disableStickyRoutes=true 时,本次请求仍使用 sticky session 状态,
|
|
177
|
+
* 但不继承 sticky target,允许后续路由重新选择 provider。
|
|
178
|
+
*/
|
|
179
|
+
disableStickyRoutes?: boolean;
|
|
175
180
|
/**
|
|
176
181
|
* 允许的 provider 白名单
|
|
177
182
|
*/
|
|
@@ -228,7 +233,7 @@ export interface RoutingFeatures {
|
|
|
228
233
|
hasCodingTool: boolean;
|
|
229
234
|
hasThinkingKeyword: boolean;
|
|
230
235
|
estimatedTokens: number;
|
|
231
|
-
lastAssistantToolCategory?: 'read' | 'write' | 'search' | 'other';
|
|
236
|
+
lastAssistantToolCategory?: 'read' | 'write' | 'search' | 'websearch' | 'other';
|
|
232
237
|
lastAssistantToolSnippet?: string;
|
|
233
238
|
lastAssistantToolLabel?: string;
|
|
234
239
|
latestMessageFromUser?: boolean;
|
|
@@ -343,3 +348,28 @@ export interface ProviderErrorEvent {
|
|
|
343
348
|
export interface FeatureBuilder {
|
|
344
349
|
build(request: StandardizedRequest, metadata: RouterMetadataInput): RoutingFeatures;
|
|
345
350
|
}
|
|
351
|
+
export interface ProviderCooldownState {
|
|
352
|
+
providerKey: string;
|
|
353
|
+
cooldownExpiresAt: number;
|
|
354
|
+
reason?: string;
|
|
355
|
+
}
|
|
356
|
+
export interface VirtualRouterHealthSnapshot {
|
|
357
|
+
providers: ProviderHealthState[];
|
|
358
|
+
cooldowns: ProviderCooldownState[];
|
|
359
|
+
}
|
|
360
|
+
export interface VirtualRouterHealthStore {
|
|
361
|
+
/**
|
|
362
|
+
* 在 VirtualRouterEngine 初始化时提供上一次持久化的健康快照。
|
|
363
|
+
* 调用方应仅返回仍在有效期内的 cooldown/熔断信息,或返回 null 表示无可恢复状态。
|
|
364
|
+
*/
|
|
365
|
+
loadInitialSnapshot(): VirtualRouterHealthSnapshot | null;
|
|
366
|
+
/**
|
|
367
|
+
* 当 VirtualRouterEngine 更新 provider 健康状态或 cooldown 时,可选地持久化最新快照。
|
|
368
|
+
* 实现应保证内部吞掉 I/O 错误,不影响路由主流程。
|
|
369
|
+
*/
|
|
370
|
+
persistSnapshot?(snapshot: VirtualRouterHealthSnapshot): void;
|
|
371
|
+
/**
|
|
372
|
+
* 可选:记录原始 ProviderErrorEvent,便于后续离线统计与诊断。
|
|
373
|
+
*/
|
|
374
|
+
recordProviderError?(event: ProviderErrorEvent): void;
|
|
375
|
+
}
|
|
@@ -94,6 +94,9 @@ function resolveRouteHint(adapterContext, flowId) {
|
|
|
94
94
|
if (!routeId) {
|
|
95
95
|
return undefined;
|
|
96
96
|
}
|
|
97
|
+
if (routeId.toLowerCase() === 'default') {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
97
100
|
if (flowId && routeId.toLowerCase() === flowId.toLowerCase()) {
|
|
98
101
|
return undefined;
|
|
99
102
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|