@jsonstudio/llms 0.6.6 → 0.6.54
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-glm.json +17 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +36 -0
- package/dist/conversion/compat/profiles/chat-lmstudio.json +37 -0
- package/dist/conversion/compat/profiles/chat-qwen.json +18 -0
- package/dist/conversion/compat/profiles/responses-c4m.json +45 -0
- package/dist/conversion/config/compat-profiles.json +38 -0
- package/dist/conversion/config/sample-config.json +314 -0
- package/dist/conversion/config/version-switch.json +150 -0
- package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +4 -0
- package/dist/conversion/hub/pipeline/compat/compat-engine.js +667 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-store.d.ts +2 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +76 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +62 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.js +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +76 -28
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +10 -12
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.d.ts +14 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +23 -0
- package/dist/conversion/hub/response/provider-response.js +18 -0
- package/dist/conversion/hub/response/response-mappers.d.ts +1 -1
- package/dist/conversion/hub/response/response-mappers.js +2 -12
- package/dist/conversion/shared/responses-output-builder.js +22 -43
- package/dist/conversion/shared/responses-response-utils.js +1 -47
- package/dist/conversion/shared/text-markup-normalizer.js +2 -2
- package/dist/conversion/shared/tool-canonicalizer.js +16 -118
- package/dist/conversion/shared/tool-mapping.js +0 -30
- package/dist/filters/config/openai-openai.fieldmap.json +18 -0
- package/dist/router/virtual-router/bootstrap.js +16 -7
- package/dist/router/virtual-router/classifier.js +40 -37
- package/dist/router/virtual-router/default-thinking-keywords.d.ts +1 -0
- package/dist/router/virtual-router/default-thinking-keywords.js +13 -0
- package/dist/router/virtual-router/features.js +340 -11
- package/dist/router/virtual-router/token-counter.d.ts +2 -0
- package/dist/router/virtual-router/token-counter.js +105 -0
- package/dist/router/virtual-router/types.d.ts +2 -0
- package/dist/router/virtual-router/types.js +2 -2
- package/dist/sse/sse-to-json/builders/response-builder.js +1 -0
- package/package.json +3 -3
|
@@ -1,5 +1,125 @@
|
|
|
1
|
+
import { countRequestTokens } from './token-counter.js';
|
|
1
2
|
const THINKING_KEYWORDS = ['let me think', 'chain of thought', 'cot', 'reason step', 'deliberate'];
|
|
2
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 SHELL_WRITE_PATTERNS = [
|
|
47
|
+
'apply_patch',
|
|
48
|
+
'sed -i',
|
|
49
|
+
'perl -pi',
|
|
50
|
+
'tee ',
|
|
51
|
+
'cat <<',
|
|
52
|
+
'cat >',
|
|
53
|
+
'printf >',
|
|
54
|
+
'touch ',
|
|
55
|
+
'truncate',
|
|
56
|
+
'mkdir',
|
|
57
|
+
'mktemp',
|
|
58
|
+
'rmdir',
|
|
59
|
+
'rm ',
|
|
60
|
+
'rm-',
|
|
61
|
+
'unlink',
|
|
62
|
+
'mv ',
|
|
63
|
+
'cp ',
|
|
64
|
+
'ln -',
|
|
65
|
+
'chmod',
|
|
66
|
+
'chown',
|
|
67
|
+
'chgrp',
|
|
68
|
+
'tar ',
|
|
69
|
+
'git add',
|
|
70
|
+
'git commit',
|
|
71
|
+
'git apply',
|
|
72
|
+
'git am',
|
|
73
|
+
'git rebase',
|
|
74
|
+
'git checkout',
|
|
75
|
+
'git merge',
|
|
76
|
+
'patch <<',
|
|
77
|
+
'npm install',
|
|
78
|
+
'pnpm install',
|
|
79
|
+
'yarn add',
|
|
80
|
+
'yarn install',
|
|
81
|
+
'pip install',
|
|
82
|
+
'pip3 install',
|
|
83
|
+
'brew install',
|
|
84
|
+
'cargo add',
|
|
85
|
+
'cargo install',
|
|
86
|
+
'go install',
|
|
87
|
+
'make install'
|
|
88
|
+
];
|
|
89
|
+
const SHELL_SEARCH_PATTERNS = [
|
|
90
|
+
'rg ',
|
|
91
|
+
'rg-',
|
|
92
|
+
'grep ',
|
|
93
|
+
'grep-',
|
|
94
|
+
'ripgrep',
|
|
95
|
+
'find ',
|
|
96
|
+
'fd ',
|
|
97
|
+
'locate ',
|
|
98
|
+
'search ',
|
|
99
|
+
'ack ',
|
|
100
|
+
'ag ',
|
|
101
|
+
'where ',
|
|
102
|
+
'which ',
|
|
103
|
+
'codesearch'
|
|
104
|
+
];
|
|
105
|
+
const SHELL_READ_PATTERNS = [
|
|
106
|
+
'ls',
|
|
107
|
+
'dir ',
|
|
108
|
+
'pwd',
|
|
109
|
+
'cat ',
|
|
110
|
+
'type ',
|
|
111
|
+
'head ',
|
|
112
|
+
'tail ',
|
|
113
|
+
'stat',
|
|
114
|
+
'tree',
|
|
115
|
+
'wc ',
|
|
116
|
+
'du ',
|
|
117
|
+
'printf "',
|
|
118
|
+
'python - <<',
|
|
119
|
+
'python -c',
|
|
120
|
+
'node - <<',
|
|
121
|
+
'node -e'
|
|
122
|
+
];
|
|
3
123
|
export function buildRoutingFeatures(request, metadata) {
|
|
4
124
|
const latestUserMessage = getLatestUserMessage(request.messages);
|
|
5
125
|
const assistantMessages = request.messages.filter((msg) => msg.role === 'assistant');
|
|
@@ -7,17 +127,18 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
7
127
|
const normalizedUserText = latestUserText.toLowerCase();
|
|
8
128
|
const hasTools = Array.isArray(request.tools) && request.tools.length > 0;
|
|
9
129
|
const hasToolCallResponses = assistantMessages.some((msg) => Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0);
|
|
10
|
-
const estimatedTokens =
|
|
130
|
+
const estimatedTokens = computeRequestTokens(request, latestUserText);
|
|
11
131
|
const hasThinking = detectKeyword(normalizedUserText, THINKING_KEYWORDS);
|
|
12
132
|
const hasVisionTool = detectVisionTool(request);
|
|
13
133
|
const hasImageAttachment = hasVisionTool && detectImageAttachment(latestUserMessage);
|
|
14
134
|
const hasCodingTool = detectCodingTool(request);
|
|
15
135
|
const hasWebTool = detectWebTool(request);
|
|
16
136
|
const hasThinkingKeyword = hasThinking || detectExtendedThinkingKeyword(normalizedUserText);
|
|
137
|
+
const lastAssistantTool = detectLastAssistantToolCategory(assistantMessages);
|
|
17
138
|
return {
|
|
18
139
|
requestId: metadata.requestId,
|
|
19
140
|
model: request.model,
|
|
20
|
-
totalMessages:
|
|
141
|
+
totalMessages: request.messages?.length ?? 0,
|
|
21
142
|
userTextSample: latestUserText.slice(0, 2000),
|
|
22
143
|
toolCount: request.tools?.length ?? 0,
|
|
23
144
|
hasTools,
|
|
@@ -28,6 +149,8 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
28
149
|
hasCodingTool,
|
|
29
150
|
hasThinkingKeyword,
|
|
30
151
|
estimatedTokens,
|
|
152
|
+
lastAssistantToolCategory: lastAssistantTool?.category,
|
|
153
|
+
lastAssistantToolName: lastAssistantTool?.name,
|
|
31
154
|
metadata: {
|
|
32
155
|
...metadata
|
|
33
156
|
}
|
|
@@ -97,12 +220,15 @@ function detectCodingTool(request) {
|
|
|
97
220
|
return false;
|
|
98
221
|
}
|
|
99
222
|
return request.tools.some((tool) => {
|
|
100
|
-
const functionName = extractToolName(tool);
|
|
101
|
-
const description = extractToolDescription(tool);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
223
|
+
const functionName = extractToolName(tool).toLowerCase();
|
|
224
|
+
const description = (extractToolDescription(tool) || '').toLowerCase();
|
|
225
|
+
if (!functionName && !description) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
if (WRITE_TOOL_EXACT.has(functionName)) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
return WRITE_TOOL_KEYWORDS.some((keyword) => functionName.includes(keyword.toLowerCase()) || description.includes(keyword.toLowerCase()));
|
|
106
232
|
});
|
|
107
233
|
}
|
|
108
234
|
function detectWebTool(request) {
|
|
@@ -125,12 +251,20 @@ function detectExtendedThinkingKeyword(text) {
|
|
|
125
251
|
const keywords = ['仔细分析', '思考', '超级思考', '深度思考', 'careful analysis', 'deep thinking', 'deliberate'];
|
|
126
252
|
return keywords.some((keyword) => text.includes(keyword));
|
|
127
253
|
}
|
|
128
|
-
function
|
|
254
|
+
function computeRequestTokens(request, fallbackText) {
|
|
255
|
+
try {
|
|
256
|
+
return countRequestTokens(request);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return fallbackEstimateTokens(fallbackText, request.messages?.length ?? 0);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function fallbackEstimateTokens(text, messageCount) {
|
|
129
263
|
if (!text) {
|
|
130
|
-
return Math.max(32, messageCount * 16);
|
|
264
|
+
return Math.max(32, Math.max(messageCount, 1) * 16);
|
|
131
265
|
}
|
|
132
266
|
const rough = Math.ceil(text.length / 4);
|
|
133
|
-
return Math.max(rough, messageCount * 32);
|
|
267
|
+
return Math.max(rough, Math.max(messageCount, 1) * 32);
|
|
134
268
|
}
|
|
135
269
|
function extractToolName(tool) {
|
|
136
270
|
if (!tool || typeof tool !== 'object') {
|
|
@@ -160,3 +294,198 @@ function extractToolDescription(tool) {
|
|
|
160
294
|
}
|
|
161
295
|
return '';
|
|
162
296
|
}
|
|
297
|
+
function detectLastAssistantToolCategory(messages) {
|
|
298
|
+
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
299
|
+
const msg = messages[idx];
|
|
300
|
+
if (!msg || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
let fallback;
|
|
304
|
+
for (const call of msg.tool_calls) {
|
|
305
|
+
const classification = classifyToolCall(call);
|
|
306
|
+
if (!classification) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (!fallback) {
|
|
310
|
+
fallback = classification;
|
|
311
|
+
}
|
|
312
|
+
if (classification.category !== 'other') {
|
|
313
|
+
return classification;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (fallback) {
|
|
317
|
+
return fallback;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
function classifyToolCall(call) {
|
|
323
|
+
if (!call || typeof call !== 'object') {
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
const functionName = typeof call?.function?.name === 'string' && call.function.name.trim()
|
|
327
|
+
? canonicalizeToolName(call.function.name)
|
|
328
|
+
: '';
|
|
329
|
+
if (!functionName) {
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
const argsObject = parseToolArguments(call?.function?.arguments);
|
|
333
|
+
const commandText = extractCommandText(argsObject);
|
|
334
|
+
const nameCategory = categorizeToolName(functionName);
|
|
335
|
+
if (nameCategory === 'write' || nameCategory === 'read' || nameCategory === 'search') {
|
|
336
|
+
return { category: nameCategory, name: functionName };
|
|
337
|
+
}
|
|
338
|
+
if (SHELL_TOOL_NAMES.has(functionName)) {
|
|
339
|
+
const shellCategory = classifyShellCommand(commandText);
|
|
340
|
+
return { category: shellCategory, name: functionName };
|
|
341
|
+
}
|
|
342
|
+
if (commandText) {
|
|
343
|
+
const derivedCategory = classifyShellCommand(commandText);
|
|
344
|
+
if (derivedCategory !== 'other') {
|
|
345
|
+
return { category: derivedCategory, name: functionName };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return { category: 'other', name: functionName };
|
|
349
|
+
}
|
|
350
|
+
function canonicalizeToolName(rawName) {
|
|
351
|
+
const trimmed = rawName.trim();
|
|
352
|
+
const markerIndex = trimmed.indexOf('arg_');
|
|
353
|
+
if (markerIndex > 0) {
|
|
354
|
+
return trimmed.slice(0, markerIndex);
|
|
355
|
+
}
|
|
356
|
+
return trimmed;
|
|
357
|
+
}
|
|
358
|
+
function parseToolArguments(rawArguments) {
|
|
359
|
+
if (!rawArguments) {
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
if (typeof rawArguments === 'string') {
|
|
363
|
+
try {
|
|
364
|
+
return JSON.parse(rawArguments);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return rawArguments;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (typeof rawArguments === 'object') {
|
|
371
|
+
return rawArguments;
|
|
372
|
+
}
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
function extractCommandText(args) {
|
|
376
|
+
if (!args) {
|
|
377
|
+
return '';
|
|
378
|
+
}
|
|
379
|
+
if (typeof args === 'string') {
|
|
380
|
+
return args;
|
|
381
|
+
}
|
|
382
|
+
if (Array.isArray(args)) {
|
|
383
|
+
return args.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
384
|
+
}
|
|
385
|
+
if (typeof args === 'object') {
|
|
386
|
+
const record = args;
|
|
387
|
+
const command = record.command;
|
|
388
|
+
const input = record.input;
|
|
389
|
+
const nestedArgs = record.args;
|
|
390
|
+
if (typeof command === 'string') {
|
|
391
|
+
return command;
|
|
392
|
+
}
|
|
393
|
+
if (Array.isArray(command)) {
|
|
394
|
+
return command.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
395
|
+
}
|
|
396
|
+
if (typeof input === 'string') {
|
|
397
|
+
return input;
|
|
398
|
+
}
|
|
399
|
+
if (typeof nestedArgs === 'string') {
|
|
400
|
+
return nestedArgs;
|
|
401
|
+
}
|
|
402
|
+
if (Array.isArray(nestedArgs)) {
|
|
403
|
+
return nestedArgs.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return '';
|
|
407
|
+
}
|
|
408
|
+
function categorizeToolName(name) {
|
|
409
|
+
const normalized = name.toLowerCase();
|
|
410
|
+
if (SEARCH_TOOL_EXACT.has(normalized) ||
|
|
411
|
+
SEARCH_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
412
|
+
return 'search';
|
|
413
|
+
}
|
|
414
|
+
if (READ_TOOL_EXACT.has(normalized) ||
|
|
415
|
+
READ_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
416
|
+
return 'read';
|
|
417
|
+
}
|
|
418
|
+
if (WRITE_TOOL_EXACT.has(normalized) ||
|
|
419
|
+
WRITE_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
420
|
+
return 'write';
|
|
421
|
+
}
|
|
422
|
+
return 'other';
|
|
423
|
+
}
|
|
424
|
+
function classifyShellCommand(command) {
|
|
425
|
+
if (!command) {
|
|
426
|
+
return 'other';
|
|
427
|
+
}
|
|
428
|
+
if (SHELL_HEREDOC_PATTERN.test(command)) {
|
|
429
|
+
return 'write';
|
|
430
|
+
}
|
|
431
|
+
const segments = splitCommandSegments(command).map(stripShellWrapper);
|
|
432
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_WRITE_PATTERNS))) {
|
|
433
|
+
return 'write';
|
|
434
|
+
}
|
|
435
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_SEARCH_PATTERNS))) {
|
|
436
|
+
return 'search';
|
|
437
|
+
}
|
|
438
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_READ_PATTERNS))) {
|
|
439
|
+
return 'read';
|
|
440
|
+
}
|
|
441
|
+
const stripped = stripShellWrapper(command);
|
|
442
|
+
if (matchesAnyPattern(stripped, SHELL_WRITE_PATTERNS)) {
|
|
443
|
+
return 'write';
|
|
444
|
+
}
|
|
445
|
+
if (matchesAnyPattern(stripped, SHELL_SEARCH_PATTERNS)) {
|
|
446
|
+
return 'search';
|
|
447
|
+
}
|
|
448
|
+
if (matchesAnyPattern(stripped, SHELL_READ_PATTERNS)) {
|
|
449
|
+
return 'read';
|
|
450
|
+
}
|
|
451
|
+
return 'other';
|
|
452
|
+
}
|
|
453
|
+
function splitCommandSegments(command) {
|
|
454
|
+
return command
|
|
455
|
+
.split(/(?:\r?\n|&&|\|\||;)/)
|
|
456
|
+
.map((segment) => segment.trim())
|
|
457
|
+
.filter(Boolean);
|
|
458
|
+
}
|
|
459
|
+
function matchesAnyPattern(text, patterns) {
|
|
460
|
+
if (!text) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
const trimmed = text.trim().toLowerCase();
|
|
464
|
+
const normalized = trimmed.startsWith('sudo ') ? trimmed.slice(5).trim() : trimmed;
|
|
465
|
+
return patterns.some((pattern) => {
|
|
466
|
+
const lowered = pattern.toLowerCase().trim();
|
|
467
|
+
return normalized.startsWith(lowered);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
function stripShellWrapper(segment) {
|
|
471
|
+
let normalized = segment.trim();
|
|
472
|
+
const wrappers = ['bash -lc ', 'bash -lc', 'sh -c ', 'sh -c', '/bin/sh -c ', '/bin/sh -c', 'env -i bash -lc ', 'env -i bash -lc'];
|
|
473
|
+
for (const wrapper of wrappers) {
|
|
474
|
+
if (normalized.toLowerCase().startsWith(wrapper)) {
|
|
475
|
+
normalized = normalized.slice(wrapper.length).trim();
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
normalized = trimEnclosingQuotes(normalized);
|
|
480
|
+
if (normalized.startsWith('sudo ')) {
|
|
481
|
+
normalized = normalized.slice(5).trim();
|
|
482
|
+
}
|
|
483
|
+
return normalized;
|
|
484
|
+
}
|
|
485
|
+
function trimEnclosingQuotes(value) {
|
|
486
|
+
if ((value.startsWith('"') && value.endsWith('"') && value.length > 1) ||
|
|
487
|
+
(value.startsWith("'") && value.endsWith("'") && value.length > 1)) {
|
|
488
|
+
return value.slice(1, -1).trim();
|
|
489
|
+
}
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { encoding_for_model, get_encoding } from 'tiktoken';
|
|
2
|
+
const DEFAULT_ENCODING = 'cl100k_base';
|
|
3
|
+
const encoderCache = new Map();
|
|
4
|
+
let defaultEncoder = null;
|
|
5
|
+
function getEncoder(model) {
|
|
6
|
+
if (model) {
|
|
7
|
+
const normalized = model.trim();
|
|
8
|
+
if (encoderCache.has(normalized)) {
|
|
9
|
+
return encoderCache.get(normalized);
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const encoder = encoding_for_model(normalized);
|
|
13
|
+
encoderCache.set(normalized, encoder);
|
|
14
|
+
return encoder;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// fall back to default encoder
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (!defaultEncoder) {
|
|
21
|
+
defaultEncoder = get_encoding(DEFAULT_ENCODING);
|
|
22
|
+
}
|
|
23
|
+
return defaultEncoder;
|
|
24
|
+
}
|
|
25
|
+
export function countRequestTokens(request) {
|
|
26
|
+
const encoder = getEncoder(request.model);
|
|
27
|
+
let total = 0;
|
|
28
|
+
for (const message of request.messages || []) {
|
|
29
|
+
total += countMessageTokens(message, encoder);
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(request.tools)) {
|
|
32
|
+
for (const tool of request.tools) {
|
|
33
|
+
total += encodeText(JSON.stringify(tool ?? {}), encoder);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (request.parameters) {
|
|
37
|
+
total += encodeText(JSON.stringify(request.parameters), encoder);
|
|
38
|
+
}
|
|
39
|
+
if (request.metadata) {
|
|
40
|
+
total += encodeText(JSON.stringify(request.metadata), encoder);
|
|
41
|
+
}
|
|
42
|
+
return total;
|
|
43
|
+
}
|
|
44
|
+
function countMessageTokens(message, encoder) {
|
|
45
|
+
let total = 0;
|
|
46
|
+
total += encodeText(message.role, encoder);
|
|
47
|
+
total += encodeContent(message.content, encoder);
|
|
48
|
+
if (Array.isArray(message.tool_calls)) {
|
|
49
|
+
for (const call of message.tool_calls) {
|
|
50
|
+
total += encodeText(JSON.stringify(call ?? {}), encoder);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(message.metadata?.toolRuns)) {
|
|
54
|
+
total += encodeText(JSON.stringify(message.metadata?.toolRuns), encoder);
|
|
55
|
+
}
|
|
56
|
+
if (message.name) {
|
|
57
|
+
total += encodeText(message.name, encoder);
|
|
58
|
+
}
|
|
59
|
+
if (message.metadata) {
|
|
60
|
+
total += encodeText(JSON.stringify(message.metadata), encoder);
|
|
61
|
+
}
|
|
62
|
+
if (message.tool_call_id) {
|
|
63
|
+
total += encodeText(message.tool_call_id, encoder);
|
|
64
|
+
}
|
|
65
|
+
return total;
|
|
66
|
+
}
|
|
67
|
+
function encodeContent(content, encoder) {
|
|
68
|
+
if (content === null || content === undefined) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
if (typeof content === 'string') {
|
|
72
|
+
return encodeText(content, encoder);
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(content)) {
|
|
75
|
+
let total = 0;
|
|
76
|
+
for (const part of content) {
|
|
77
|
+
if (typeof part === 'string') {
|
|
78
|
+
total += encodeText(part, encoder);
|
|
79
|
+
}
|
|
80
|
+
else if (part && typeof part === 'object') {
|
|
81
|
+
if (typeof part.text === 'string') {
|
|
82
|
+
total += encodeText(part.text, encoder);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
total += encodeText(JSON.stringify(part), encoder);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return total;
|
|
90
|
+
}
|
|
91
|
+
if (typeof content === 'object') {
|
|
92
|
+
return encodeText(JSON.stringify(content), encoder);
|
|
93
|
+
}
|
|
94
|
+
return encodeText(String(content), encoder);
|
|
95
|
+
}
|
|
96
|
+
function encodeText(value, encoder) {
|
|
97
|
+
if (value === null || value === undefined) {
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
const text = typeof value === 'string' ? value : String(value);
|
|
101
|
+
if (!text.trim()) {
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
return encoder.encode(text).length;
|
|
105
|
+
}
|
|
@@ -111,6 +111,8 @@ export interface RoutingFeatures {
|
|
|
111
111
|
hasCodingTool: boolean;
|
|
112
112
|
hasThinkingKeyword: boolean;
|
|
113
113
|
estimatedTokens: number;
|
|
114
|
+
lastAssistantToolCategory?: 'read' | 'write' | 'search' | 'other';
|
|
115
|
+
lastAssistantToolName?: string;
|
|
114
116
|
metadata: RouterMetadataInput;
|
|
115
117
|
}
|
|
116
118
|
export interface ClassificationResult {
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsonstudio/llms",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.054",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "node scripts/bump-version.mjs && tsc -p tsconfig.json",
|
|
10
|
-
"build:dev": "node scripts/bump-version.mjs && tsc -p tsconfig.json",
|
|
9
|
+
"build": "node scripts/bump-version.mjs && tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
|
|
10
|
+
"build:dev": "node scripts/bump-version.mjs && tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
|
|
11
11
|
"lint": "eslint --no-eslintrc -c .eslintrc.json src --ext .ts --no-cache",
|
|
12
12
|
"lint:fix": "eslint --no-eslintrc -c .eslintrc.json src --ext .ts --no-cache --fix",
|
|
13
13
|
"postbuild": "node scripts/tests/run-matrix-ci.mjs",
|