@jsonstudio/llms 0.6.147 → 0.6.198
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 +194 -26
- package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -35
- package/dist/conversion/compat/profiles/chat-qwen.json +20 -16
- package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
- package/dist/conversion/hub/pipeline/compat/compat-engine.js +429 -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 +23 -15
- 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/bridge-actions.js +1 -1
- package/dist/conversion/shared/bridge-policies.js +0 -1
- 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 +239 -39
- package/dist/router/virtual-router/classifier.js +19 -51
- 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 -27
- package/dist/router/virtual-router/engine.js +191 -396
- package/dist/router/virtual-router/features.js +24 -607
- 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/token-file-scanner.d.ts +15 -0
- package/dist/router/virtual-router/token-file-scanner.js +56 -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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function getLatestUserMessage(messages) {
|
|
2
|
+
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
3
|
+
if (messages[idx]?.role === 'user') {
|
|
4
|
+
return messages[idx];
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
export function getLatestMessageRole(messages) {
|
|
10
|
+
if (!Array.isArray(messages) || !messages.length) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const last = messages[messages.length - 1];
|
|
14
|
+
if (last && typeof last.role === 'string' && last.role.trim()) {
|
|
15
|
+
return last.role.trim();
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
export function extractMessageText(message) {
|
|
20
|
+
if (typeof message.content === 'string' && message.content.trim()) {
|
|
21
|
+
return message.content;
|
|
22
|
+
}
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
export function detectKeyword(text, keywords) {
|
|
26
|
+
if (!text)
|
|
27
|
+
return false;
|
|
28
|
+
return keywords.some((keyword) => text.includes(keyword.toLowerCase()));
|
|
29
|
+
}
|
|
30
|
+
export function detectExtendedThinkingKeyword(text) {
|
|
31
|
+
if (!text) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const keywords = ['仔细分析', '思考', '超级思考', '深度思考', 'careful analysis', 'deep thinking', 'deliberate'];
|
|
35
|
+
return keywords.some((keyword) => text.includes(keyword));
|
|
36
|
+
}
|
|
37
|
+
export function detectImageAttachment(message) {
|
|
38
|
+
if (!message)
|
|
39
|
+
return false;
|
|
40
|
+
if (!message.metadata || typeof message.metadata !== 'object') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const meta = message.metadata;
|
|
44
|
+
const attachments = (meta.attachments ?? null);
|
|
45
|
+
if (Array.isArray(attachments)) {
|
|
46
|
+
return attachments.some((attachment) => {
|
|
47
|
+
const candidate = attachment;
|
|
48
|
+
const typeValue = typeof candidate.type === 'string' ? candidate.type.toLowerCase() : '';
|
|
49
|
+
const urlValue = typeof candidate.url === 'string'
|
|
50
|
+
? candidate.url
|
|
51
|
+
: typeof candidate.src === 'string'
|
|
52
|
+
? candidate.src
|
|
53
|
+
: typeof candidate.image_url === 'string'
|
|
54
|
+
? candidate.image_url
|
|
55
|
+
: typeof candidate.image_url?.url === 'string'
|
|
56
|
+
? candidate.image_url.url
|
|
57
|
+
: '';
|
|
58
|
+
return typeValue.includes('image') && urlValue.trim().length > 0;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (typeof meta.attachmentType === 'string' && meta.attachmentType.toLowerCase().includes('image')) {
|
|
62
|
+
const urlCandidate = typeof meta.attachmentUrl === 'string' ? meta.attachmentUrl : '';
|
|
63
|
+
return urlCandidate.trim().length > 0;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
@@ -42,7 +42,9 @@ export class ProviderRegistry {
|
|
|
42
42
|
runtimeKey: profile.runtimeKey,
|
|
43
43
|
modelId,
|
|
44
44
|
processMode: profile.processMode || 'chat',
|
|
45
|
-
responsesConfig: profile.responsesConfig
|
|
45
|
+
responsesConfig: profile.responsesConfig,
|
|
46
|
+
streaming: profile.streaming,
|
|
47
|
+
maxContextTokens: profile.maxContextTokens
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
static normalizeProfile(key, profile) {
|
|
@@ -58,7 +60,9 @@ export class ProviderRegistry {
|
|
|
58
60
|
runtimeKey: profile.runtimeKey,
|
|
59
61
|
modelId,
|
|
60
62
|
processMode: profile.processMode || 'chat',
|
|
61
|
-
responsesConfig: profile.responsesConfig
|
|
63
|
+
responsesConfig: profile.responsesConfig,
|
|
64
|
+
streaming: profile.streaming,
|
|
65
|
+
maxContextTokens: profile.maxContextTokens
|
|
62
66
|
};
|
|
63
67
|
}
|
|
64
68
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { countRequestTokens } from './token-counter.js';
|
|
2
|
+
export function computeRequestTokens(request, fallbackText) {
|
|
3
|
+
try {
|
|
4
|
+
return countRequestTokens(request);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return fallbackEstimateTokens(fallbackText, request.messages?.length ?? 0);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function fallbackEstimateTokens(text, messageCount) {
|
|
11
|
+
if (!text) {
|
|
12
|
+
return Math.max(32, Math.max(messageCount, 1) * 16);
|
|
13
|
+
}
|
|
14
|
+
const rough = Math.ceil(text.length / 4);
|
|
15
|
+
return Math.max(rough, Math.max(messageCount, 1) * 32);
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface OAuthTokenFileMatch {
|
|
2
|
+
filePath: string;
|
|
3
|
+
sequence: number;
|
|
4
|
+
alias: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 扫描本地 RouteCodex auth 目录中的 OAuth token 文件。
|
|
8
|
+
*
|
|
9
|
+
* 约定:
|
|
10
|
+
* - 目录: ~/.routecodex/auth
|
|
11
|
+
* - 文件名: <provider>-oauth-<sequence>[-<alias>].json
|
|
12
|
+
*
|
|
13
|
+
* 仅在 Node 环境下使用;如果环境不满足,返回空列表。
|
|
14
|
+
*/
|
|
15
|
+
export declare function scanOAuthTokenFiles(oauthProviderId: string, authDir?: string): OAuthTokenFileMatch[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
const TOKEN_FILE_PATTERN = /^([a-z0-9_-]+)-oauth-(\d+)(?:-(.+))?\.json$/i;
|
|
5
|
+
/**
|
|
6
|
+
* 扫描本地 RouteCodex auth 目录中的 OAuth token 文件。
|
|
7
|
+
*
|
|
8
|
+
* 约定:
|
|
9
|
+
* - 目录: ~/.routecodex/auth
|
|
10
|
+
* - 文件名: <provider>-oauth-<sequence>[-<alias>].json
|
|
11
|
+
*
|
|
12
|
+
* 仅在 Node 环境下使用;如果环境不满足,返回空列表。
|
|
13
|
+
*/
|
|
14
|
+
export function scanOAuthTokenFiles(oauthProviderId, authDir) {
|
|
15
|
+
if (!isNodeEnvironment()) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const provider = oauthProviderId.trim().toLowerCase();
|
|
19
|
+
if (!provider) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const baseDir = authDir && authDir.trim()
|
|
23
|
+
? authDir.trim()
|
|
24
|
+
: path.join(os.homedir(), '.routecodex', 'auth');
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = fs.readdirSync(baseDir);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const matches = [];
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (!entry.endsWith('.json'))
|
|
35
|
+
continue;
|
|
36
|
+
const match = entry.match(TOKEN_FILE_PATTERN);
|
|
37
|
+
if (!match)
|
|
38
|
+
continue;
|
|
39
|
+
const [, providerPrefix, sequenceStr, alias] = match;
|
|
40
|
+
if (providerPrefix.toLowerCase() !== provider)
|
|
41
|
+
continue;
|
|
42
|
+
const sequence = parseInt(sequenceStr, 10);
|
|
43
|
+
if (!Number.isFinite(sequence) || sequence <= 0)
|
|
44
|
+
continue;
|
|
45
|
+
matches.push({
|
|
46
|
+
filePath: path.join(baseDir, entry),
|
|
47
|
+
sequence,
|
|
48
|
+
alias: alias || 'default'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
matches.sort((a, b) => a.sequence - b.sequence);
|
|
52
|
+
return matches;
|
|
53
|
+
}
|
|
54
|
+
function isNodeEnvironment() {
|
|
55
|
+
return typeof process !== 'undefined' && !!process.release && process.release.name === 'node';
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StandardizedMessage, StandardizedRequest } from '../../conversion/hub/types/standardized.js';
|
|
2
|
+
export type ToolCategory = 'read' | 'write' | 'search' | 'other';
|
|
3
|
+
export type ToolClassification = {
|
|
4
|
+
category: ToolCategory;
|
|
5
|
+
name: string;
|
|
6
|
+
commandSnippet?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function detectVisionTool(request: StandardizedRequest): boolean;
|
|
9
|
+
export declare function detectCodingTool(request: StandardizedRequest): boolean;
|
|
10
|
+
export declare function detectWebTool(request: StandardizedRequest): boolean;
|
|
11
|
+
export declare function extractMeaningfulDeclaredToolNames(tools: StandardizedRequest['tools'] | undefined): string[];
|
|
12
|
+
export declare function detectLastAssistantToolCategory(messages: StandardizedMessage[]): ToolClassification | undefined;
|
|
13
|
+
export declare function canonicalizeToolName(rawName: string): string;
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
const WEB_TOOL_KEYWORDS = ['websearch', 'web_search', 'web-search', 'webfetch', 'web_fetch', 'web_request', 'search_web', 'internet_search'];
|
|
2
|
+
const READ_TOOL_EXACT = new Set([
|
|
3
|
+
'read_file',
|
|
4
|
+
'read_text',
|
|
5
|
+
'view_file',
|
|
6
|
+
'view_code',
|
|
7
|
+
'view_document',
|
|
8
|
+
'open_file',
|
|
9
|
+
'get_file',
|
|
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'
|
|
19
|
+
]);
|
|
20
|
+
const WRITE_TOOL_EXACT = new Set([
|
|
21
|
+
'apply_patch',
|
|
22
|
+
'write_file',
|
|
23
|
+
'create_file',
|
|
24
|
+
'modify_file',
|
|
25
|
+
'edit_file',
|
|
26
|
+
'update_file',
|
|
27
|
+
'save_file',
|
|
28
|
+
'append_file',
|
|
29
|
+
'replace_file',
|
|
30
|
+
'delete_file',
|
|
31
|
+
'remove_file',
|
|
32
|
+
'rename_file',
|
|
33
|
+
'move_file',
|
|
34
|
+
'copy_file',
|
|
35
|
+
'mkdir',
|
|
36
|
+
'rmdir'
|
|
37
|
+
]);
|
|
38
|
+
const SEARCH_TOOL_EXACT = new Set(['websearch', 'web_search', 'search_web', 'internet_search', 'webfetch', 'web_fetch']);
|
|
39
|
+
const READ_TOOL_KEYWORDS = ['read', 'list', 'view', 'download', 'open', 'show', 'fetch', 'inspect'];
|
|
40
|
+
const WRITE_TOOL_KEYWORDS = ['write', 'patch', 'modify', 'edit', 'create', 'update', 'append', 'replace', 'delete', 'remove'];
|
|
41
|
+
const SEARCH_TOOL_KEYWORDS = ['search', 'websearch', 'web_fetch', 'webfetch', 'web-request', 'web_request', 'internet'];
|
|
42
|
+
const SHELL_TOOL_NAMES = new Set(['shell_command', 'shell', 'bash']);
|
|
43
|
+
const DECLARED_TOOL_IGNORE = new Set(['exec_command']);
|
|
44
|
+
const SHELL_HEREDOC_PATTERN = /<<\s*['"]?[a-z0-9_-]+/i;
|
|
45
|
+
const SHELL_WRITE_PATTERNS = [
|
|
46
|
+
'apply_patch',
|
|
47
|
+
'sed -i',
|
|
48
|
+
'perl -pi',
|
|
49
|
+
'tee ',
|
|
50
|
+
'cat <<',
|
|
51
|
+
'cat >',
|
|
52
|
+
'printf >',
|
|
53
|
+
'touch ',
|
|
54
|
+
'truncate',
|
|
55
|
+
'mkdir',
|
|
56
|
+
'mktemp',
|
|
57
|
+
'rmdir',
|
|
58
|
+
'rm ',
|
|
59
|
+
'rm-',
|
|
60
|
+
'unlink',
|
|
61
|
+
'mv ',
|
|
62
|
+
'cp ',
|
|
63
|
+
'ln -',
|
|
64
|
+
'chmod',
|
|
65
|
+
'chown',
|
|
66
|
+
'chgrp',
|
|
67
|
+
'tar ',
|
|
68
|
+
'git add',
|
|
69
|
+
'git commit',
|
|
70
|
+
'git apply',
|
|
71
|
+
'git am',
|
|
72
|
+
'git rebase',
|
|
73
|
+
'git checkout',
|
|
74
|
+
'git merge',
|
|
75
|
+
'patch <<',
|
|
76
|
+
'npm install',
|
|
77
|
+
'pnpm install',
|
|
78
|
+
'yarn add',
|
|
79
|
+
'yarn install',
|
|
80
|
+
'pip install',
|
|
81
|
+
'pip3 install',
|
|
82
|
+
'brew install',
|
|
83
|
+
'cargo add',
|
|
84
|
+
'cargo install',
|
|
85
|
+
'go install',
|
|
86
|
+
'make install'
|
|
87
|
+
];
|
|
88
|
+
const SHELL_SEARCH_PATTERNS = [
|
|
89
|
+
'rg ',
|
|
90
|
+
'rg-',
|
|
91
|
+
'grep ',
|
|
92
|
+
'grep-',
|
|
93
|
+
'ripgrep',
|
|
94
|
+
'find ',
|
|
95
|
+
'fd ',
|
|
96
|
+
'locate ',
|
|
97
|
+
'search ',
|
|
98
|
+
'ack ',
|
|
99
|
+
'ag ',
|
|
100
|
+
'where ',
|
|
101
|
+
'which ',
|
|
102
|
+
'codesearch'
|
|
103
|
+
];
|
|
104
|
+
const SHELL_READ_PATTERNS = [
|
|
105
|
+
'ls',
|
|
106
|
+
'dir ',
|
|
107
|
+
'pwd',
|
|
108
|
+
'cat ',
|
|
109
|
+
'type ',
|
|
110
|
+
'head ',
|
|
111
|
+
'tail ',
|
|
112
|
+
'stat',
|
|
113
|
+
'tree',
|
|
114
|
+
'wc ',
|
|
115
|
+
'du ',
|
|
116
|
+
'printf "',
|
|
117
|
+
'python - <<',
|
|
118
|
+
'python -c',
|
|
119
|
+
'node - <<',
|
|
120
|
+
'node -e'
|
|
121
|
+
];
|
|
122
|
+
export function detectVisionTool(request) {
|
|
123
|
+
if (!Array.isArray(request.tools)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return request.tools.some((tool) => {
|
|
127
|
+
const functionName = extractToolName(tool);
|
|
128
|
+
const description = extractToolDescription(tool);
|
|
129
|
+
return /vision|image|picture|photo/i.test(functionName) || /vision|image|picture|photo/i.test(description || '');
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
export function detectCodingTool(request) {
|
|
133
|
+
if (!Array.isArray(request.tools)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return request.tools.some((tool) => {
|
|
137
|
+
const functionName = extractToolName(tool).toLowerCase();
|
|
138
|
+
const description = (extractToolDescription(tool) || '').toLowerCase();
|
|
139
|
+
if (!functionName && !description) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (WRITE_TOOL_EXACT.has(functionName)) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return WRITE_TOOL_KEYWORDS.some((keyword) => functionName.includes(keyword.toLowerCase()) || description.includes(keyword.toLowerCase()));
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
export function detectWebTool(request) {
|
|
149
|
+
if (!Array.isArray(request.tools)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return request.tools.some((tool) => {
|
|
153
|
+
const functionName = extractToolName(tool);
|
|
154
|
+
const description = extractToolDescription(tool);
|
|
155
|
+
const normalizedName = functionName.toLowerCase();
|
|
156
|
+
const normalizedDesc = (description || '').toLowerCase();
|
|
157
|
+
return (WEB_TOOL_KEYWORDS.some((keyword) => normalizedName.includes(keyword)) ||
|
|
158
|
+
WEB_TOOL_KEYWORDS.some((keyword) => normalizedDesc.includes(keyword)));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
export function extractMeaningfulDeclaredToolNames(tools) {
|
|
162
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const names = [];
|
|
166
|
+
for (const tool of tools) {
|
|
167
|
+
const rawName = extractToolName(tool);
|
|
168
|
+
if (!rawName) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const canonical = canonicalizeToolName(rawName).toLowerCase();
|
|
172
|
+
if (!canonical || DECLARED_TOOL_IGNORE.has(canonical)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
names.push(rawName);
|
|
176
|
+
}
|
|
177
|
+
return names;
|
|
178
|
+
}
|
|
179
|
+
export function detectLastAssistantToolCategory(messages) {
|
|
180
|
+
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
181
|
+
const msg = messages[idx];
|
|
182
|
+
if (!msg || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
let fallback;
|
|
186
|
+
for (const call of msg.tool_calls) {
|
|
187
|
+
const classification = classifyToolCall(call);
|
|
188
|
+
if (!classification) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!fallback) {
|
|
192
|
+
fallback = classification;
|
|
193
|
+
}
|
|
194
|
+
if (classification.category !== 'other') {
|
|
195
|
+
return classification;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (fallback) {
|
|
199
|
+
return fallback;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
function classifyToolCall(call) {
|
|
205
|
+
if (!call || typeof call !== 'object') {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
const functionName = typeof call?.function?.name === 'string' && call.function.name.trim()
|
|
209
|
+
? canonicalizeToolName(call.function.name)
|
|
210
|
+
: '';
|
|
211
|
+
if (!functionName) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
const argsObject = parseToolArguments(call?.function?.arguments);
|
|
215
|
+
const commandText = extractCommandText(argsObject);
|
|
216
|
+
const nameCategory = categorizeToolName(functionName);
|
|
217
|
+
const snippet = buildCommandSnippet(commandText);
|
|
218
|
+
if (nameCategory === 'write' || nameCategory === 'read' || nameCategory === 'search') {
|
|
219
|
+
return { category: nameCategory, name: functionName, commandSnippet: snippet };
|
|
220
|
+
}
|
|
221
|
+
if (SHELL_TOOL_NAMES.has(functionName)) {
|
|
222
|
+
const shellCategory = classifyShellCommand(commandText);
|
|
223
|
+
return { category: shellCategory, name: functionName, commandSnippet: snippet };
|
|
224
|
+
}
|
|
225
|
+
if (commandText) {
|
|
226
|
+
const derivedCategory = classifyShellCommand(commandText);
|
|
227
|
+
if (derivedCategory !== 'other') {
|
|
228
|
+
return { category: derivedCategory, name: functionName, commandSnippet: snippet };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { category: 'other', name: functionName, commandSnippet: snippet };
|
|
232
|
+
}
|
|
233
|
+
function extractToolName(tool) {
|
|
234
|
+
if (!tool || typeof tool !== 'object') {
|
|
235
|
+
return '';
|
|
236
|
+
}
|
|
237
|
+
const candidate = tool;
|
|
238
|
+
const fromFunction = candidate.function;
|
|
239
|
+
if (fromFunction && typeof fromFunction.name === 'string' && fromFunction.name.trim()) {
|
|
240
|
+
return fromFunction.name;
|
|
241
|
+
}
|
|
242
|
+
if (typeof candidate.name === 'string' && candidate.name.trim()) {
|
|
243
|
+
return candidate.name;
|
|
244
|
+
}
|
|
245
|
+
return '';
|
|
246
|
+
}
|
|
247
|
+
function extractToolDescription(tool) {
|
|
248
|
+
if (!tool || typeof tool !== 'object') {
|
|
249
|
+
return '';
|
|
250
|
+
}
|
|
251
|
+
const candidate = tool;
|
|
252
|
+
const fromFunction = candidate.function;
|
|
253
|
+
if (fromFunction && typeof fromFunction.description === 'string' && fromFunction.description.trim()) {
|
|
254
|
+
return fromFunction.description;
|
|
255
|
+
}
|
|
256
|
+
if (typeof candidate.description === 'string' && candidate.description.trim()) {
|
|
257
|
+
return candidate.description;
|
|
258
|
+
}
|
|
259
|
+
return '';
|
|
260
|
+
}
|
|
261
|
+
export function canonicalizeToolName(rawName) {
|
|
262
|
+
const trimmed = rawName.trim();
|
|
263
|
+
const markerIndex = trimmed.indexOf('arg_');
|
|
264
|
+
if (markerIndex > 0) {
|
|
265
|
+
return trimmed.slice(0, markerIndex);
|
|
266
|
+
}
|
|
267
|
+
return trimmed;
|
|
268
|
+
}
|
|
269
|
+
function parseToolArguments(rawArguments) {
|
|
270
|
+
if (!rawArguments) {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
if (typeof rawArguments === 'string') {
|
|
274
|
+
try {
|
|
275
|
+
return JSON.parse(rawArguments);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return rawArguments;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (typeof rawArguments === 'object') {
|
|
282
|
+
return rawArguments;
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
function extractCommandText(args) {
|
|
287
|
+
if (!args) {
|
|
288
|
+
return '';
|
|
289
|
+
}
|
|
290
|
+
if (typeof args === 'string') {
|
|
291
|
+
return args;
|
|
292
|
+
}
|
|
293
|
+
if (Array.isArray(args)) {
|
|
294
|
+
return args.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
295
|
+
}
|
|
296
|
+
if (typeof args === 'object') {
|
|
297
|
+
const record = args;
|
|
298
|
+
const stringKeys = ['command', 'cmd', 'input', 'code', 'script', 'text', 'prompt'];
|
|
299
|
+
for (const key of stringKeys) {
|
|
300
|
+
const value = record[key];
|
|
301
|
+
if (typeof value === 'string' && value.trim()) {
|
|
302
|
+
return value;
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(value)) {
|
|
305
|
+
const joined = value.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
306
|
+
if (joined.trim()) {
|
|
307
|
+
return joined;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const nestedArgs = record.args;
|
|
312
|
+
if (typeof nestedArgs === 'string' && nestedArgs.trim()) {
|
|
313
|
+
return nestedArgs;
|
|
314
|
+
}
|
|
315
|
+
if (Array.isArray(nestedArgs)) {
|
|
316
|
+
const joined = nestedArgs.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
317
|
+
if (joined.trim()) {
|
|
318
|
+
return joined;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return '';
|
|
323
|
+
}
|
|
324
|
+
function buildCommandSnippet(commandText) {
|
|
325
|
+
if (!commandText) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
const collapsed = commandText.replace(/\s+/g, ' ').trim();
|
|
329
|
+
if (!collapsed) {
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
const limit = 80;
|
|
333
|
+
if (collapsed.length <= limit) {
|
|
334
|
+
return collapsed;
|
|
335
|
+
}
|
|
336
|
+
return `${collapsed.slice(0, limit)}…`;
|
|
337
|
+
}
|
|
338
|
+
function categorizeToolName(name) {
|
|
339
|
+
const normalized = name.toLowerCase();
|
|
340
|
+
if (SEARCH_TOOL_EXACT.has(normalized) ||
|
|
341
|
+
SEARCH_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
342
|
+
return 'search';
|
|
343
|
+
}
|
|
344
|
+
if (READ_TOOL_EXACT.has(normalized) ||
|
|
345
|
+
READ_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
346
|
+
return 'read';
|
|
347
|
+
}
|
|
348
|
+
if (WRITE_TOOL_EXACT.has(normalized) ||
|
|
349
|
+
WRITE_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
350
|
+
return 'write';
|
|
351
|
+
}
|
|
352
|
+
return 'other';
|
|
353
|
+
}
|
|
354
|
+
function classifyShellCommand(command) {
|
|
355
|
+
if (!command) {
|
|
356
|
+
return 'other';
|
|
357
|
+
}
|
|
358
|
+
if (SHELL_HEREDOC_PATTERN.test(command)) {
|
|
359
|
+
return 'write';
|
|
360
|
+
}
|
|
361
|
+
const segments = splitCommandSegments(command).map(stripShellWrapper);
|
|
362
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_WRITE_PATTERNS))) {
|
|
363
|
+
return 'write';
|
|
364
|
+
}
|
|
365
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_SEARCH_PATTERNS))) {
|
|
366
|
+
return 'search';
|
|
367
|
+
}
|
|
368
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_READ_PATTERNS))) {
|
|
369
|
+
return 'read';
|
|
370
|
+
}
|
|
371
|
+
const stripped = stripShellWrapper(command);
|
|
372
|
+
if (matchesAnyPattern(stripped, SHELL_WRITE_PATTERNS)) {
|
|
373
|
+
return 'write';
|
|
374
|
+
}
|
|
375
|
+
if (matchesAnyPattern(stripped, SHELL_SEARCH_PATTERNS)) {
|
|
376
|
+
return 'search';
|
|
377
|
+
}
|
|
378
|
+
if (matchesAnyPattern(stripped, SHELL_READ_PATTERNS)) {
|
|
379
|
+
return 'read';
|
|
380
|
+
}
|
|
381
|
+
return 'other';
|
|
382
|
+
}
|
|
383
|
+
function splitCommandSegments(command) {
|
|
384
|
+
return command
|
|
385
|
+
.split(/(?:\r?\n|&&|\|\||;)/)
|
|
386
|
+
.map((segment) => segment.trim())
|
|
387
|
+
.filter(Boolean);
|
|
388
|
+
}
|
|
389
|
+
function stripShellWrapper(command) {
|
|
390
|
+
if (!command) {
|
|
391
|
+
return '';
|
|
392
|
+
}
|
|
393
|
+
const wrappers = ['bash -lc', 'sh -c', 'zsh -c'];
|
|
394
|
+
for (const wrapper of wrappers) {
|
|
395
|
+
if (command.startsWith(wrapper)) {
|
|
396
|
+
return command.slice(wrapper.length).trim();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return command.trim();
|
|
400
|
+
}
|
|
401
|
+
function matchesAnyPattern(command, patterns) {
|
|
402
|
+
return patterns.some((pattern) => command.includes(pattern));
|
|
403
|
+
}
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* Virtual Router 类型定义
|
|
3
3
|
*/
|
|
4
4
|
import type { StandardizedRequest } from '../../conversion/hub/types/standardized.js';
|
|
5
|
+
export declare const DEFAULT_MODEL_CONTEXT_TOKENS = 200000;
|
|
5
6
|
export declare const DEFAULT_ROUTE = "default";
|
|
6
7
|
export declare const ROUTE_PRIORITY: string[];
|
|
7
8
|
export type RoutingPools = Record<string, string[]>;
|
|
9
|
+
export type StreamingPreference = 'auto' | 'always' | 'never';
|
|
8
10
|
export interface ProviderAuthConfig {
|
|
9
11
|
type: 'apiKey' | 'oauth';
|
|
10
12
|
secretRef?: string;
|
|
@@ -32,6 +34,8 @@ export interface ProviderProfile {
|
|
|
32
34
|
modelId?: string;
|
|
33
35
|
processMode?: 'chat' | 'passthrough';
|
|
34
36
|
responsesConfig?: ResponsesProviderConfig;
|
|
37
|
+
streaming?: StreamingPreference;
|
|
38
|
+
maxContextTokens?: number;
|
|
35
39
|
}
|
|
36
40
|
export interface ProviderRuntimeProfile {
|
|
37
41
|
runtimeKey: string;
|
|
@@ -46,6 +50,11 @@ export interface ProviderRuntimeProfile {
|
|
|
46
50
|
modelId?: string;
|
|
47
51
|
processMode?: 'chat' | 'passthrough';
|
|
48
52
|
responsesConfig?: ResponsesProviderConfig;
|
|
53
|
+
streaming?: StreamingPreference;
|
|
54
|
+
modelStreaming?: Record<string, StreamingPreference>;
|
|
55
|
+
modelContextTokens?: Record<string, number>;
|
|
56
|
+
defaultContextTokens?: number;
|
|
57
|
+
maxContextTokens?: number;
|
|
49
58
|
}
|
|
50
59
|
export interface VirtualRouterClassifierConfig {
|
|
51
60
|
longContextThresholdTokens?: number;
|
|
@@ -69,6 +78,12 @@ export interface VirtualRouterConfig {
|
|
|
69
78
|
classifier: VirtualRouterClassifierConfig;
|
|
70
79
|
loadBalancing?: LoadBalancingPolicy;
|
|
71
80
|
health?: ProviderHealthConfig;
|
|
81
|
+
contextRouting?: VirtualRouterContextRoutingConfig;
|
|
82
|
+
}
|
|
83
|
+
export interface VirtualRouterContextRoutingConfig {
|
|
84
|
+
warnRatio: number;
|
|
85
|
+
hardLimit?: boolean;
|
|
86
|
+
fallbackRoute?: string;
|
|
72
87
|
}
|
|
73
88
|
export type VirtualRouterProviderDefinition = Record<string, unknown>;
|
|
74
89
|
export interface VirtualRouterBootstrapInput extends Record<string, unknown> {
|
|
@@ -78,6 +93,7 @@ export interface VirtualRouterBootstrapInput extends Record<string, unknown> {
|
|
|
78
93
|
classifier?: VirtualRouterClassifierConfig;
|
|
79
94
|
loadBalancing?: LoadBalancingPolicy;
|
|
80
95
|
health?: ProviderHealthConfig;
|
|
96
|
+
contextRouting?: VirtualRouterContextRoutingConfig;
|
|
81
97
|
}
|
|
82
98
|
export type ProviderRuntimeMap = Record<string, ProviderRuntimeProfile>;
|
|
83
99
|
export interface VirtualRouterBootstrapResult {
|
|
@@ -117,10 +133,9 @@ export interface RoutingFeatures {
|
|
|
117
133
|
hasThinkingKeyword: boolean;
|
|
118
134
|
estimatedTokens: number;
|
|
119
135
|
lastAssistantToolCategory?: 'read' | 'write' | 'search' | 'other';
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
assistantCalledWebSearchTool?: boolean;
|
|
136
|
+
lastAssistantToolSnippet?: string;
|
|
137
|
+
lastAssistantToolLabel?: string;
|
|
138
|
+
latestMessageFromUser?: boolean;
|
|
124
139
|
metadata: RouterMetadataInput;
|
|
125
140
|
}
|
|
126
141
|
export interface ClassificationResult {
|
|
@@ -147,12 +162,11 @@ export interface TargetMetadata {
|
|
|
147
162
|
modelId: string;
|
|
148
163
|
processMode?: 'chat' | 'passthrough';
|
|
149
164
|
responsesConfig?: ResponsesProviderConfig;
|
|
165
|
+
streaming?: StreamingPreference;
|
|
166
|
+
maxContextTokens?: number;
|
|
150
167
|
}
|
|
151
|
-
export type ResponsesStreamingMode = 'auto' | 'always' | 'never';
|
|
152
168
|
export interface ResponsesProviderConfig {
|
|
153
169
|
toolCallIdStyle?: 'fc' | 'preserve';
|
|
154
|
-
streaming?: ResponsesStreamingMode;
|
|
155
|
-
instructionsMode?: 'default' | 'inline';
|
|
156
170
|
}
|
|
157
171
|
export declare enum VirtualRouterErrorCode {
|
|
158
172
|
NO_STANDARDIZED_REQUEST = "NO_STANDARDIZED_REQUEST",
|