@jsonstudio/llms 0.6.568 → 0.6.626
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/profiles/chat-gemini.json +15 -15
- package/dist/conversion/compat/profiles/chat-glm.json +194 -194
- package/dist/conversion/compat/profiles/chat-iflow.json +199 -199
- package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
- package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
- package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/dist/conversion/compat/profiles/responses-output2choices-test.json +9 -10
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +0 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +68 -69
- 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 +37 -16
- package/dist/conversion/hub/response/provider-response.js +0 -8
- package/dist/conversion/hub/response/response-runtime.js +47 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +59 -4
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +93 -12
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +208 -31
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +280 -14
- package/dist/conversion/hub/standardized-bridge.js +11 -2
- package/dist/conversion/hub/types/chat-envelope.d.ts +10 -0
- package/dist/conversion/hub/types/standardized.d.ts +2 -1
- 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/text-markup-normalizer.d.ts +20 -0
- package/dist/conversion/shared/text-markup-normalizer.js +84 -5
- package/dist/conversion/shared/tool-filter-pipeline.d.ts +1 -1
- package/dist/conversion/shared/tool-filter-pipeline.js +54 -29
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +15 -7
- package/dist/filters/special/response-tool-arguments-toon-decode.js +108 -22
- package/dist/guidance/index.js +2 -0
- package/dist/router/virtual-router/classifier.js +16 -12
- package/dist/router/virtual-router/engine.js +45 -4
- package/dist/router/virtual-router/tool-signals.d.ts +2 -1
- package/dist/router/virtual-router/tool-signals.js +293 -134
- package/dist/router/virtual-router/types.d.ts +1 -1
- package/dist/router/virtual-router/types.js +1 -1
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +28 -4
- package/dist/sse/json-to-sse/event-generators/responses.js +9 -2
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +7 -3
- package/dist/tools/apply-patch-structured.js +4 -3
- package/package.json +2 -2
|
@@ -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,101 +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
|
-
'sed -n',
|
|
106
|
-
'sed --quiet',
|
|
107
|
-
'sed ',
|
|
108
|
-
'rg ',
|
|
109
|
-
' ripgrep',
|
|
110
|
-
'grep ',
|
|
111
|
-
'egrep ',
|
|
112
|
-
'fgrep ',
|
|
113
|
-
'ag ',
|
|
114
|
-
'ack ',
|
|
115
|
-
'find ',
|
|
116
|
-
'nl ',
|
|
117
|
-
'less',
|
|
118
|
-
'more',
|
|
119
|
-
'awk ',
|
|
120
|
-
'perl -ne',
|
|
121
|
-
'perl -pe',
|
|
122
|
-
'strings '
|
|
123
|
-
];
|
|
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]+/;
|
|
124
77
|
export function detectVisionTool(request) {
|
|
125
78
|
if (!Array.isArray(request.tools)) {
|
|
126
79
|
return false;
|
|
@@ -178,31 +131,46 @@ export function extractMeaningfulDeclaredToolNames(tools) {
|
|
|
178
131
|
}
|
|
179
132
|
return names;
|
|
180
133
|
}
|
|
134
|
+
const TOOL_CATEGORY_PRIORITY = {
|
|
135
|
+
websearch: 4,
|
|
136
|
+
read: 3,
|
|
137
|
+
write: 2,
|
|
138
|
+
search: 1,
|
|
139
|
+
other: 0
|
|
140
|
+
};
|
|
181
141
|
export function detectLastAssistantToolCategory(messages) {
|
|
182
142
|
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
183
143
|
const msg = messages[idx];
|
|
184
144
|
if (!msg || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0) {
|
|
185
145
|
continue;
|
|
186
146
|
}
|
|
187
|
-
|
|
147
|
+
const candidates = [];
|
|
188
148
|
for (const call of msg.tool_calls) {
|
|
189
149
|
const classification = classifyToolCall(call);
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
if (!fallback) {
|
|
194
|
-
fallback = classification;
|
|
195
|
-
}
|
|
196
|
-
if (classification.category !== 'other') {
|
|
197
|
-
return classification;
|
|
150
|
+
if (classification) {
|
|
151
|
+
candidates.push(classification);
|
|
198
152
|
}
|
|
199
153
|
}
|
|
200
|
-
if (
|
|
201
|
-
|
|
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
|
+
}
|
|
202
166
|
}
|
|
167
|
+
return best;
|
|
203
168
|
}
|
|
204
169
|
return undefined;
|
|
205
170
|
}
|
|
171
|
+
export function classifyToolCallForReport(call) {
|
|
172
|
+
return classifyToolCall(call);
|
|
173
|
+
}
|
|
206
174
|
function classifyToolCall(call) {
|
|
207
175
|
if (!call || typeof call !== 'object') {
|
|
208
176
|
return undefined;
|
|
@@ -218,9 +186,8 @@ function classifyToolCall(call) {
|
|
|
218
186
|
const snippet = buildCommandSnippet(commandText);
|
|
219
187
|
const normalizedName = functionName.toLowerCase();
|
|
220
188
|
const normalizedCmd = commandText.toLowerCase();
|
|
221
|
-
// 1) Web search
|
|
222
|
-
const isWebSearch = WEB_TOOL_KEYWORDS.some((keyword) => normalizedName.includes(keyword))
|
|
223
|
-
WEB_TOOL_KEYWORDS.some((keyword) => normalizedCmd.includes(keyword));
|
|
189
|
+
// 1) Web search 优先:函数名命中 web 搜索关键字时,一律归类为 websearch,优先级最高。
|
|
190
|
+
const isWebSearch = WEB_TOOL_KEYWORDS.some((keyword) => normalizedName.includes(keyword));
|
|
224
191
|
// 2) 基于工具名的初步分类(read / write / search / other)
|
|
225
192
|
const nameCategory = categorizeToolName(functionName);
|
|
226
193
|
// 3) shell_command / exec_command 根据内部命令判断读写性质
|
|
@@ -236,7 +203,7 @@ function classifyToolCall(call) {
|
|
|
236
203
|
// 5. 其它工具
|
|
237
204
|
// Priority 1: Web search
|
|
238
205
|
if (isWebSearch) {
|
|
239
|
-
return { category: '
|
|
206
|
+
return { category: 'websearch', name: functionName, commandSnippet: snippet };
|
|
240
207
|
}
|
|
241
208
|
// Priority 2: Write (写文件) — 名称或内部命令任一判断为写,都按写处理
|
|
242
209
|
if (nameCategory === 'write' || shellCategory === 'write') {
|
|
@@ -247,13 +214,13 @@ function classifyToolCall(call) {
|
|
|
247
214
|
return { category: 'read', name: functionName, commandSnippet: snippet };
|
|
248
215
|
}
|
|
249
216
|
// Priority 4: 其他 search 类工具(非 web search)
|
|
250
|
-
if (nameCategory === 'search') {
|
|
217
|
+
if (nameCategory === 'search' || shellCategory === 'search') {
|
|
251
218
|
return { category: 'search', name: functionName, commandSnippet: snippet };
|
|
252
219
|
}
|
|
253
|
-
// Priority 5: 兜底用命令文本再判断一次 shell
|
|
220
|
+
// Priority 5: 兜底用命令文本再判断一次 shell 风格读写/搜索(非 shell/exec_command 的工具)
|
|
254
221
|
if (!SHELL_TOOL_NAMES.has(functionName) && functionName !== 'exec_command' && commandText) {
|
|
255
222
|
const derivedCategory = classifyShellCommand(commandText);
|
|
256
|
-
if (derivedCategory === 'write' || derivedCategory === 'read') {
|
|
223
|
+
if (derivedCategory === 'write' || derivedCategory === 'read' || derivedCategory === 'search') {
|
|
257
224
|
return { category: derivedCategory, name: functionName, commandSnippet: snippet };
|
|
258
225
|
}
|
|
259
226
|
}
|
|
@@ -371,12 +338,10 @@ function categorizeToolName(name) {
|
|
|
371
338
|
SEARCH_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
372
339
|
return 'search';
|
|
373
340
|
}
|
|
374
|
-
if (READ_TOOL_EXACT.has(normalized)
|
|
375
|
-
READ_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
341
|
+
if (READ_TOOL_EXACT.has(normalized)) {
|
|
376
342
|
return 'read';
|
|
377
343
|
}
|
|
378
|
-
if (WRITE_TOOL_EXACT.has(normalized)
|
|
379
|
-
WRITE_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
344
|
+
if (WRITE_TOOL_EXACT.has(normalized)) {
|
|
380
345
|
return 'write';
|
|
381
346
|
}
|
|
382
347
|
return 'other';
|
|
@@ -388,28 +353,225 @@ function classifyShellCommand(command) {
|
|
|
388
353
|
if (SHELL_HEREDOC_PATTERN.test(command)) {
|
|
389
354
|
return 'write';
|
|
390
355
|
}
|
|
391
|
-
const segments = splitCommandSegments(command)
|
|
392
|
-
|
|
393
|
-
|
|
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
|
+
}
|
|
394
382
|
}
|
|
395
|
-
if (
|
|
383
|
+
if (sawRead) {
|
|
396
384
|
return 'read';
|
|
397
385
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return 'write';
|
|
401
|
-
}
|
|
402
|
-
if (matchesAnyPattern(stripped, SHELL_READ_PATTERNS)) {
|
|
403
|
-
return 'read';
|
|
386
|
+
if (sawSearch) {
|
|
387
|
+
return 'search';
|
|
404
388
|
}
|
|
405
389
|
return 'other';
|
|
406
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
|
+
}
|
|
407
558
|
function splitCommandSegments(command) {
|
|
408
559
|
return command
|
|
409
560
|
.split(/(?:\r?\n|&&|\|\||;)/)
|
|
410
561
|
.map((segment) => segment.trim())
|
|
411
562
|
.filter(Boolean);
|
|
412
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
|
+
}
|
|
413
575
|
function stripShellWrapper(command) {
|
|
414
576
|
if (!command) {
|
|
415
577
|
return '';
|
|
@@ -422,6 +584,3 @@ function stripShellWrapper(command) {
|
|
|
422
584
|
}
|
|
423
585
|
return command.trim();
|
|
424
586
|
}
|
|
425
|
-
function matchesAnyPattern(command, patterns) {
|
|
426
|
-
return patterns.some((pattern) => command.includes(pattern));
|
|
427
|
-
}
|
|
@@ -233,7 +233,7 @@ export interface RoutingFeatures {
|
|
|
233
233
|
hasCodingTool: boolean;
|
|
234
234
|
hasThinkingKeyword: boolean;
|
|
235
235
|
estimatedTokens: number;
|
|
236
|
-
lastAssistantToolCategory?: 'read' | 'write' | 'search' | 'other';
|
|
236
|
+
lastAssistantToolCategory?: 'read' | 'write' | 'search' | 'websearch' | 'other';
|
|
237
237
|
lastAssistantToolSnippet?: string;
|
|
238
238
|
lastAssistantToolLabel?: string;
|
|
239
239
|
latestMessageFromUser?: boolean;
|
|
@@ -49,9 +49,7 @@ const handler = async (ctx) => {
|
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
const contentRaw = message.content;
|
|
52
|
-
const contentText = typeof contentRaw === 'string'
|
|
53
|
-
? contentRaw.trim()
|
|
54
|
-
: '';
|
|
52
|
+
const contentText = typeof contentRaw === 'string' ? contentRaw.trim() : '';
|
|
55
53
|
if (contentText.length > 0) {
|
|
56
54
|
return null;
|
|
57
55
|
}
|
|
@@ -59,10 +57,35 @@ const handler = async (ctx) => {
|
|
|
59
57
|
if (toolCalls.length > 0) {
|
|
60
58
|
return null;
|
|
61
59
|
}
|
|
60
|
+
// 统计连续空回复次数,超过上限后不再自动续写,而是返回一个可重试错误。
|
|
61
|
+
const previousCountRaw = adapterRecord.geminiEmptyReplyCount;
|
|
62
|
+
const previousCount = typeof previousCountRaw === 'number' && Number.isFinite(previousCountRaw) && previousCountRaw >= 0
|
|
63
|
+
? previousCountRaw
|
|
64
|
+
: 0;
|
|
65
|
+
const nextCount = previousCount + 1;
|
|
62
66
|
const captured = getCapturedRequest(ctx.adapterContext);
|
|
63
67
|
if (!captured) {
|
|
64
68
|
return null;
|
|
65
69
|
}
|
|
70
|
+
// 超过最多 3 次空回复:返回一个 HTTP_HANDLER_ERROR 形状的错误,交由上层错误中心处理。
|
|
71
|
+
if (nextCount > 3) {
|
|
72
|
+
const errorChat = {
|
|
73
|
+
id: base.id,
|
|
74
|
+
object: base.object,
|
|
75
|
+
model: base.model,
|
|
76
|
+
error: {
|
|
77
|
+
message: 'fetch failed: gemini_empty_reply_continue exceeded max empty replies',
|
|
78
|
+
code: 'HTTP_HANDLER_ERROR',
|
|
79
|
+
type: 'servertool_empty_reply'
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
chatResponse: errorChat,
|
|
84
|
+
execution: {
|
|
85
|
+
flowId: FLOW_ID
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
66
89
|
const followupPayload = buildContinueFollowupPayload(captured);
|
|
67
90
|
if (!followupPayload) {
|
|
68
91
|
return null;
|
|
@@ -76,7 +99,8 @@ const handler = async (ctx) => {
|
|
|
76
99
|
payload: followupPayload,
|
|
77
100
|
metadata: {
|
|
78
101
|
serverToolFollowup: true,
|
|
79
|
-
stream: false
|
|
102
|
+
stream: false,
|
|
103
|
+
geminiEmptyReplyCount: nextCount
|
|
80
104
|
}
|
|
81
105
|
}
|
|
82
106
|
}
|
|
@@ -81,9 +81,16 @@ function normalizeUsage(usage) {
|
|
|
81
81
|
return fallback;
|
|
82
82
|
}
|
|
83
83
|
const asAny = usage;
|
|
84
|
-
const
|
|
84
|
+
const baseInputRaw = Number((asAny.input_tokens ?? asAny.prompt_tokens));
|
|
85
|
+
const baseInput = Number.isFinite(baseInputRaw) ? baseInputRaw : 0;
|
|
86
|
+
let cachedRaw = Number(asAny.cache_read_input_tokens);
|
|
87
|
+
if (!Number.isFinite(cachedRaw) && asAny.input_tokens_details && typeof asAny.input_tokens_details === 'object') {
|
|
88
|
+
const details = asAny.input_tokens_details;
|
|
89
|
+
cachedRaw = Number(details.cached_tokens);
|
|
90
|
+
}
|
|
91
|
+
const cached = Number.isFinite(cachedRaw) ? cachedRaw : 0;
|
|
92
|
+
const input = baseInput + cached;
|
|
85
93
|
const outputRaw = Number((asAny.output_tokens ?? asAny.completion_tokens));
|
|
86
|
-
const input = Number.isFinite(inputRaw) ? inputRaw : 0;
|
|
87
94
|
const output = Number.isFinite(outputRaw) ? outputRaw : 0;
|
|
88
95
|
const totalRaw = Number(asAny.total_tokens);
|
|
89
96
|
const total = Number.isFinite(totalRaw) ? totalRaw : input + output;
|
|
@@ -111,12 +111,16 @@ export function createAnthropicResponseBuilder(options) {
|
|
|
111
111
|
break;
|
|
112
112
|
}
|
|
113
113
|
case 'message_delta': {
|
|
114
|
-
const
|
|
114
|
+
const data = event.data ?? {};
|
|
115
|
+
const delta = data?.delta;
|
|
115
116
|
if (delta?.stop_reason) {
|
|
116
117
|
state.stopReason = delta.stop_reason;
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
// 部分实现将 usage 挂在 delta.usage,部分实现挂在顶层 event.data.usage,
|
|
120
|
+
// 这里统一优先读取 delta.usage,缺失时回退到 data.usage。
|
|
121
|
+
const usageNode = (delta && delta.usage) ?? data.usage;
|
|
122
|
+
if (usageNode) {
|
|
123
|
+
state.usage = usageNode;
|
|
120
124
|
}
|
|
121
125
|
break;
|
|
122
126
|
}
|
|
@@ -216,11 +216,12 @@ export function buildStructuredPatch(payload) {
|
|
|
216
216
|
else {
|
|
217
217
|
lines.push(`*** Update File: ${file}`);
|
|
218
218
|
for (const hunk of section.hunks) {
|
|
219
|
-
|
|
219
|
+
// 每个 hunk 仅需一个开头的 "@@" 行,后面直接跟上下文/增删行。
|
|
220
220
|
for (const entry of hunk) {
|
|
221
|
-
|
|
221
|
+
if (!entry.startsWith('@@')) {
|
|
222
|
+
lines.push(entry);
|
|
223
|
+
}
|
|
222
224
|
}
|
|
223
|
-
lines.push('@@');
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
}
|