kiku-agent 1.3.2 → 1.3.3
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/README.md +69 -12
- package/package.json +2 -1
- package/server/agent/agentCore.js +319 -46
- package/server/agent/appPaths.js +31 -0
- package/server/agent/audioPlayer.js +339 -12
- package/server/agent/autoContext.js +193 -0
- package/server/agent/catalogEntityResolver.js +170 -28
- package/server/agent/clarifyingQuestions.js +137 -35
- package/server/agent/cli.js +177 -48
- package/server/agent/cloudAdapters.js +116 -0
- package/server/agent/commandRegistry.js +409 -80
- package/server/agent/commands.js +1056 -125
- package/server/agent/config.js +1088 -690
- package/server/agent/contextCompactor.js +213 -21
- package/server/agent/cpuMetrics.js +199 -0
- package/server/agent/engineService.js +591 -0
- package/server/agent/eval/modules/evaluation/runner.js +4 -14
- package/server/agent/eval/modules/evaluation/scoring.js +15 -3
- package/server/agent/eval/modules/server/evalApi.js +5 -5
- package/server/agent/eval/modules/ui/evalUi.js +7 -4
- package/server/agent/eventBus.js +1 -0
- package/server/agent/ink/main.jsx +196 -191
- package/server/agent/jobManager.js +11 -1
- package/server/agent/lanDiscovery.js +721 -23
- package/server/agent/llmClient.js +317 -71
- package/server/agent/lmStudioWakeup.js +517 -65
- package/server/agent/locationService.js +797 -0
- package/server/agent/mcpQuizData.js +509 -0
- package/server/agent/memory/contextAllocator.js +19 -7
- package/server/agent/memory.js +3 -1
- package/server/agent/modelCompatibility.js +352 -0
- package/server/agent/modelDefaults.js +1 -0
- package/server/agent/modelManager.js +46 -2
- package/server/agent/newsWatch.js +9 -3
- package/server/agent/processManager.js +229 -0
- package/server/agent/prompts.js +174 -8
- package/server/agent/routingInterceptor.js +9 -1
- package/server/agent/sanitizer.js +41 -6
- package/server/agent/searchQueryPlanner.js +202 -0
- package/server/agent/session.js +59 -4
- package/server/agent/tests/agentFollowUps.test.js +128 -2
- package/server/agent/tests/agentMusicControl.test.js +245 -0
- package/server/agent/tests/apiRouter.test.js +1 -1
- package/server/agent/tests/autoContext.test.js +176 -0
- package/server/agent/tests/bilibiliEmbed.test.js +45 -0
- package/server/agent/tests/chatHardening.test.js +121 -0
- package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
- package/server/agent/tests/clarifyingQuestions.test.js +33 -0
- package/server/agent/tests/cliAutomation.test.js +38 -1
- package/server/agent/tests/cloudProviders.test.js +91 -3
- package/server/agent/tests/commands.test.js +92 -3
- package/server/agent/tests/contextIntelligence.test.js +50 -3
- package/server/agent/tests/cpuMetrics.test.js +101 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +32 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +82 -0
- package/server/agent/tests/engineService.test.js +436 -0
- package/server/agent/tests/engineUnload.test.js +94 -0
- package/server/agent/tests/envCommand.test.js +69 -0
- package/server/agent/tests/evalMetrics.test.js +8 -0
- package/server/agent/tests/fetchApi.test.js +99 -0
- package/server/agent/tests/hybridWebSearch.test.js +2 -0
- package/server/agent/tests/lanDiscovery.test.js +5 -0
- package/server/agent/tests/lanInterfaces.test.js +510 -0
- package/server/agent/tests/lmStudioWakeup.test.js +278 -0
- package/server/agent/tests/locationTool.test.js +278 -0
- package/server/agent/tests/memoryRoute.test.js +173 -0
- package/server/agent/tests/memoryUiNormalization.test.js +75 -0
- package/server/agent/tests/modelCompatibility.test.js +148 -0
- package/server/agent/tests/modelManager.test.js +32 -0
- package/server/agent/tests/musicControlUiBridge.test.js +113 -0
- package/server/agent/tests/nextGenArchitecture.test.js +4 -2
- package/server/agent/tests/openDomainPrompt.test.js +197 -0
- package/server/agent/tests/openDomainSearch.test.js +121 -0
- package/server/agent/tests/processManager.test.js +144 -0
- package/server/agent/tests/providerPersistence.test.js +10 -5
- package/server/agent/tests/quickActionsAndModals.test.js +180 -0
- package/server/agent/tests/searchQueryPlanner.test.js +108 -0
- package/server/agent/tests/securityAuditRemediation.test.js +16 -9
- package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
- package/server/agent/tests/securityHardening.test.js +4 -4
- package/server/agent/tests/sessionAndCopy.test.js +39 -4
- package/server/agent/tests/sidebarControls.test.js +34 -0
- package/server/agent/tests/statefulMemory.test.js +8 -1
- package/server/agent/tests/textProtocolLoop.test.js +71 -0
- package/server/agent/tests/textToolParsing.test.js +48 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +41 -0
- package/server/agent/tests/unifiedEndpoints.test.js +13 -6
- package/server/agent/tests/v110Features.test.js +1 -1
- package/server/agent/tests/vramEstimator.test.js +75 -0
- package/server/agent/tests/webChatDocs.test.js +199 -0
- package/server/agent/tests/webChatInferenceControl.test.js +298 -0
- package/server/agent/tests/webChatInspection.test.js +256 -5
- package/server/agent/tests/webChatMcp.test.js +94 -0
- package/server/agent/tests/webExtract.test.js +23 -0
- package/server/agent/tests/webSources.test.js +46 -12
- package/server/agent/textToolProtocol.js +173 -46
- package/server/agent/toolRouter.js +318 -44
- package/server/agent/tools.js +877 -96
- package/server/agent/ui.js +39 -34
- package/server/agent/vramService.js +35 -54
- package/server/agent/webSearch.js +441 -162
- package/server/agent/websearch/orchestrator.js +15 -5
- package/server/agent/websearch/resilience/cache.js +9 -2
- package/server/agent/websearch/security/ssrf.js +41 -10
- package/server/agent/youtubeAuth.js +5 -20
- package/server/apiRouter.js +5558 -4422
- package/server/mcp/data/discography.js +2 -2
- package/server/mcp/examples/antigravity.example.json +17 -0
- package/server/mcp/handler.js +8 -4
- package/server/mcp/security.js +220 -21
- package/server/mcp/server.js +2 -1
- package/server/mcp/tests/connection.test.js +14 -8
- package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
- package/server/mcp/tools.js +7 -1
- package/server/supabase_migration_prefix.sql +102 -0
- package/server/supabase_schema.sql +18 -18
- package/server/web-chat/core/connection.js +179 -218
- package/server/web-chat/core/conversationContext.js +9 -2
- package/server/web-chat/core/directCatalogEngine.js +52 -28
- package/server/web-chat/core/dramaKnowledge.js +33 -22
- package/server/web-chat/core/followUpEngine.js +735 -81
- package/server/web-chat/core/futureEventGuard.js +5 -1
- package/server/web-chat/core/musicControlAction.js +70 -0
- package/server/web-chat/core/security.js +32 -21
- package/server/web-chat/core/toolInspector.js +6 -6
- package/server/web-chat/core/urlGuard.js +45 -0
- package/server/web-chat/router.js +204 -157
- package/server/web-chat/routes/chatRoute.js +885 -488
- package/server/web-chat/routes/engineRoute.js +270 -0
- package/server/web-chat/routes/exportRoute.js +1 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
- package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
- package/server/web-chat/routes/mcpRoute.js +386 -0
- package/server/web-chat/routes/memoryRoute.js +253 -0
- package/server/web-chat/routes/providerRoute.js +49 -16
- package/server/web-chat/routes/staticRoute.js +306 -17
- package/server/web-chat/routes/statusRoute.js +194 -73
- package/server/web-chat/routes/suggestRoute.js +11 -3
- package/server/web-chat/routes/verifyRoute.js +26 -4
- package/server/web-chat/server.js +7 -6
- package/server/web-chat/ui/docsBuilder.js +3558 -0
- package/server/web-chat/ui/htmlBuilder.js +12 -0
- package/server/web-chat/ui/scripts/client.js +3401 -1302
- package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
- package/server/web-chat/ui/settingsBuilder.js +7870 -0
- package/server/web-chat/ui/styles/lan-card.css +165 -0
- package/server/web-chat/ui/styles/main.css +4289 -447
- package/server/web-chat/ui/sw.js +64 -0
- package/server/web-chat/ui/templates/body.html +407 -281
- package/src/data/awardsData.js +146 -146
- package/src/data/musicVideoData.js +6 -4
- package/src/lib/vram/estimator.js +195 -19
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import {
|
|
4
|
+
buildLmsLoadArgs,
|
|
5
|
+
loadLmStudioModel,
|
|
6
|
+
unloadLmStudioModel,
|
|
7
|
+
wakeUpLmStudio,
|
|
8
|
+
probeLmStudioRuntime,
|
|
9
|
+
} from './lmStudioWakeup.js';
|
|
10
|
+
import {
|
|
11
|
+
registerBackgroundProcess,
|
|
12
|
+
getBackgroundProcess,
|
|
13
|
+
stopBackgroundProcess,
|
|
14
|
+
killWslProcessByName,
|
|
15
|
+
} from './processManager.js';
|
|
16
|
+
|
|
17
|
+
let cachedWslInfo = null;
|
|
18
|
+
let lastWslCheck = 0;
|
|
19
|
+
const WSL_CACHE_TTL_MS = 60000;
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export function detectPlatformAndWsl(bypassCache = false) {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
if (!bypassCache && cachedWslInfo && (now - lastWslCheck < WSL_CACHE_TTL_MS)) {
|
|
25
|
+
return cachedWslInfo;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const platform = process.platform;
|
|
29
|
+
let isWsl = false;
|
|
30
|
+
let distros = [];
|
|
31
|
+
let activeDistro = null;
|
|
32
|
+
let wslAvailable = false;
|
|
33
|
+
|
|
34
|
+
if (platform === 'linux') {
|
|
35
|
+
try {
|
|
36
|
+
const release = os.release().toLowerCase();
|
|
37
|
+
isWsl = release.includes('microsoft') || release.includes('wsl');
|
|
38
|
+
} catch {}
|
|
39
|
+
cachedWslInfo = {
|
|
40
|
+
platform: 'linux',
|
|
41
|
+
isNativeLinux: !isWsl,
|
|
42
|
+
isWsl,
|
|
43
|
+
wslAvailable: false,
|
|
44
|
+
distros: [],
|
|
45
|
+
activeDistro: isWsl ? 'wsl-current' : null,
|
|
46
|
+
};
|
|
47
|
+
lastWslCheck = now;
|
|
48
|
+
return cachedWslInfo;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (platform === 'darwin') {
|
|
52
|
+
cachedWslInfo = {
|
|
53
|
+
platform: 'darwin',
|
|
54
|
+
isNativeLinux: false,
|
|
55
|
+
isWsl: false,
|
|
56
|
+
wslAvailable: false,
|
|
57
|
+
distros: [],
|
|
58
|
+
activeDistro: null,
|
|
59
|
+
};
|
|
60
|
+
lastWslCheck = now;
|
|
61
|
+
return cachedWslInfo;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (platform === 'win32') {
|
|
65
|
+
try {
|
|
66
|
+
const stdout = execSync('wsl --list --quiet', {
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
timeout: 3000,
|
|
69
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
70
|
+
windowsHide: true,
|
|
71
|
+
});
|
|
72
|
+
distros = stdout
|
|
73
|
+
.split(/\r?\n/)
|
|
74
|
+
.map(d => d.replace(/\0/g, '').trim())
|
|
75
|
+
.filter(Boolean);
|
|
76
|
+
wslAvailable = distros.length > 0;
|
|
77
|
+
activeDistro = distros[0] || null;
|
|
78
|
+
} catch {
|
|
79
|
+
wslAvailable = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cachedWslInfo = {
|
|
83
|
+
platform: 'win32',
|
|
84
|
+
isNativeLinux: false,
|
|
85
|
+
isWsl: false,
|
|
86
|
+
wslAvailable,
|
|
87
|
+
distros,
|
|
88
|
+
activeDistro,
|
|
89
|
+
};
|
|
90
|
+
lastWslCheck = now;
|
|
91
|
+
return cachedWslInfo;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
cachedWslInfo = {
|
|
95
|
+
platform,
|
|
96
|
+
isNativeLinux: false,
|
|
97
|
+
isWsl: false,
|
|
98
|
+
wslAvailable: false,
|
|
99
|
+
distros: [],
|
|
100
|
+
activeDistro: null,
|
|
101
|
+
};
|
|
102
|
+
lastWslCheck = now;
|
|
103
|
+
return cachedWslInfo;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
export function clearWslCache() {
|
|
108
|
+
cachedWslInfo = null;
|
|
109
|
+
lastWslCheck = 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
export function checkEngineCompatibility(engine = 'lms', customInfo = null) {
|
|
114
|
+
const normEngine = String(engine).toLowerCase().trim();
|
|
115
|
+
const info = customInfo || detectPlatformAndWsl();
|
|
116
|
+
|
|
117
|
+
if (normEngine === 'lms' || normEngine === 'lmstudio') {
|
|
118
|
+
return {
|
|
119
|
+
supported: true,
|
|
120
|
+
engine: 'lms',
|
|
121
|
+
name: 'LM Studio (llama.cpp)',
|
|
122
|
+
platform: info.platform,
|
|
123
|
+
supportedOs: ['windows', 'macos', 'linux'],
|
|
124
|
+
targetLevel: 'consumer',
|
|
125
|
+
description: 'Consumer & workstation inference engine running on llama.cpp with universal 3-OS support.',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (normEngine === 'vllm') {
|
|
130
|
+
if (info.platform === 'linux') {
|
|
131
|
+
return {
|
|
132
|
+
supported: true,
|
|
133
|
+
isEnterprise: true,
|
|
134
|
+
engine: 'vllm',
|
|
135
|
+
name: 'vLLM (Native Linux)',
|
|
136
|
+
platform: 'linux',
|
|
137
|
+
isWsl: Boolean(info.isWsl),
|
|
138
|
+
distro: info.activeDistro,
|
|
139
|
+
supportedOs: ['linux'],
|
|
140
|
+
targetLevel: 'enterprise',
|
|
141
|
+
description: 'Enterprise-grade PagedAttention engine for high-throughput Data Center workloads.',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (info.platform === 'win32') {
|
|
146
|
+
const hasWsl = info.wslAvailable !== undefined ? Boolean(info.wslAvailable) : Boolean(info.hasWsl);
|
|
147
|
+
if (hasWsl) {
|
|
148
|
+
return {
|
|
149
|
+
supported: true,
|
|
150
|
+
isEnterprise: true,
|
|
151
|
+
viaWsl: true,
|
|
152
|
+
engine: 'vllm',
|
|
153
|
+
name: `vLLM (WSL2 ${info.activeDistro || 'Linux'})`,
|
|
154
|
+
platform: 'win32',
|
|
155
|
+
isWsl: true,
|
|
156
|
+
distro: info.activeDistro,
|
|
157
|
+
distros: info.distros || [],
|
|
158
|
+
supportedOs: ['linux'],
|
|
159
|
+
targetLevel: 'enterprise',
|
|
160
|
+
description: `Enterprise-grade PagedAttention engine running inside WSL2 Linux distribution "${info.activeDistro || 'Linux'}".`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
supported: false,
|
|
165
|
+
isEnterprise: true,
|
|
166
|
+
engine: 'vllm',
|
|
167
|
+
name: 'vLLM (WSL Required)',
|
|
168
|
+
platform: 'win32',
|
|
169
|
+
isWsl: false,
|
|
170
|
+
supportedOs: ['linux'],
|
|
171
|
+
targetLevel: 'enterprise',
|
|
172
|
+
reason: 'vLLM is an enterprise Linux-only engine. On Windows, it requires WSL2 (Windows Subsystem for Linux). Install with: wsl --install',
|
|
173
|
+
installHint: 'wsl --install',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (info.platform === 'darwin') {
|
|
178
|
+
return {
|
|
179
|
+
supported: false,
|
|
180
|
+
isEnterprise: true,
|
|
181
|
+
engine: 'vllm',
|
|
182
|
+
name: 'vLLM (Unsupported on macOS)',
|
|
183
|
+
platform: 'darwin',
|
|
184
|
+
isWsl: false,
|
|
185
|
+
supportedOs: ['linux'],
|
|
186
|
+
targetLevel: 'enterprise',
|
|
187
|
+
reason: 'vLLM is designed exclusively for Linux with CUDA/ROCm hardware. macOS is not supported natively.',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
supported: false,
|
|
194
|
+
engine: normEngine,
|
|
195
|
+
name: normEngine,
|
|
196
|
+
platform: info.platform,
|
|
197
|
+
reason: `Unknown engine: ${engine}`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
export function buildVllmServeArgs(model, options = {}) {
|
|
203
|
+
const targetModel = model || 'google/gemma-4-e4b';
|
|
204
|
+
const args = ['serve', targetModel];
|
|
205
|
+
|
|
206
|
+
const port = Number(options.port) || 8000;
|
|
207
|
+
args.push('--port', String(port));
|
|
208
|
+
|
|
209
|
+
const host = options.host || '0.0.0.0';
|
|
210
|
+
args.push('--host', host);
|
|
211
|
+
|
|
212
|
+
const memUtil = options.gpuMemoryUtilization !== undefined ? Number(options.gpuMemoryUtilization) : 0.90;
|
|
213
|
+
if (Number.isFinite(memUtil) && memUtil >= 0.1 && memUtil <= 1.0) {
|
|
214
|
+
args.push('--gpu-memory-utilization', String(memUtil));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const maxLen = Number(options.maxModelLen);
|
|
218
|
+
if (Number.isFinite(maxLen) && maxLen >= 512 && maxLen <= 131072) {
|
|
219
|
+
args.push('--max-model-len', String(Math.floor(maxLen)));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const tp = Number(options.tensorParallelSize);
|
|
223
|
+
if (Number.isFinite(tp) && tp >= 1 && tp <= 8) {
|
|
224
|
+
args.push('--tensor-parallel-size', String(Math.floor(tp)));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (options.dtype && ['auto', 'half', 'float16', 'bfloat16', 'float'].includes(String(options.dtype).toLowerCase())) {
|
|
228
|
+
args.push('--dtype', String(options.dtype).toLowerCase());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (options.enforceEager) {
|
|
232
|
+
args.push('--enforce-eager');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (options.kvCacheDtype && ['auto', 'fp8', 'fp8_e5m2', 'fp8_e4m3'].includes(String(options.kvCacheDtype).toLowerCase())) {
|
|
236
|
+
args.push('--kv-cache-dtype', String(options.kvCacheDtype).toLowerCase());
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (options.enablePrefixCaching) {
|
|
240
|
+
args.push('--enable-prefix-caching');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (options.trustRemoteCode) {
|
|
244
|
+
args.push('--trust-remote-code');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return args;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
export function buildVllmExecutionPlan(model, options = {}) {
|
|
252
|
+
const customInfo = options.customInfo || (options.platform ? {
|
|
253
|
+
platform: options.platform,
|
|
254
|
+
isWsl: options.platform === 'win32',
|
|
255
|
+
wslAvailable: options.platform === 'win32',
|
|
256
|
+
hasWsl: options.platform === 'win32',
|
|
257
|
+
activeDistro: options.distro || 'Ubuntu',
|
|
258
|
+
distros: [options.distro || 'Ubuntu'],
|
|
259
|
+
} : null);
|
|
260
|
+
const compatibility = checkEngineCompatibility('vllm', customInfo);
|
|
261
|
+
if (!compatibility.supported) {
|
|
262
|
+
return { ok: false, reason: compatibility.reason, compatibility };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const serveArgs = buildVllmServeArgs(model, options);
|
|
266
|
+
const serveCmdStr = `vllm ${serveArgs.join(' ')}`;
|
|
267
|
+
|
|
268
|
+
if (compatibility.platform === 'win32') {
|
|
269
|
+
const distro = options.distro || compatibility.distro || 'Ubuntu-26.04';
|
|
270
|
+
const customVenv = options.venvPath || process.env.VLLM_VENV_PATH || '';
|
|
271
|
+
const dynamicVenvCheck = [
|
|
272
|
+
customVenv ? `if [ -f "${customVenv}/bin/activate" ]; then source "${customVenv}/bin/activate" 2>/dev/null; fi;` : '',
|
|
273
|
+
'if ! command -v vllm >/dev/null 2>&1; then',
|
|
274
|
+
' for act in ~/.venvs/*/bin/activate ~/.venv/bin/activate ~/venv/bin/activate ./.venv/bin/activate; do',
|
|
275
|
+
' if [ -f "$act" ]; then source "$act" 2>/dev/null; if command -v vllm >/dev/null 2>&1; then break; fi; fi;',
|
|
276
|
+
' done;',
|
|
277
|
+
'fi;',
|
|
278
|
+
'export PATH="$HOME/.local/bin:/usr/local/bin:$PATH";',
|
|
279
|
+
].filter(Boolean).join(' ');
|
|
280
|
+
const fullBashCmd = `${dynamicVenvCheck} ${serveCmdStr}`;
|
|
281
|
+
const rawCommand = `wsl -d ${distro} bash -c "${fullBashCmd}"`;
|
|
282
|
+
return {
|
|
283
|
+
ok: true,
|
|
284
|
+
engine: 'vllm',
|
|
285
|
+
isWsl: true,
|
|
286
|
+
distro,
|
|
287
|
+
binary: 'wsl.exe',
|
|
288
|
+
command: 'wsl.exe',
|
|
289
|
+
args: ['-d', distro, 'bash', '-c', fullBashCmd],
|
|
290
|
+
rawCommand,
|
|
291
|
+
displayCommand: rawCommand,
|
|
292
|
+
compatibility,
|
|
293
|
+
serveArgs,
|
|
294
|
+
port: options.port || 8000,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
ok: true,
|
|
300
|
+
engine: 'vllm',
|
|
301
|
+
isWsl: Boolean(compatibility.isWsl),
|
|
302
|
+
binary: 'vllm',
|
|
303
|
+
command: 'vllm',
|
|
304
|
+
args: serveArgs,
|
|
305
|
+
rawCommand: serveCmdStr,
|
|
306
|
+
displayCommand: serveCmdStr,
|
|
307
|
+
compatibility,
|
|
308
|
+
serveArgs,
|
|
309
|
+
port: options.port || 8000,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
export async function getEnginesHealth(endpoints = {}) {
|
|
315
|
+
const lmsUrl = endpoints.lms || 'http://127.0.0.1:1234/v1';
|
|
316
|
+
const vllmUrl = endpoints.vllm || 'http://127.0.0.1:8000/v1';
|
|
317
|
+
|
|
318
|
+
const lmsCompat = checkEngineCompatibility('lms');
|
|
319
|
+
const vllmCompat = checkEngineCompatibility('vllm');
|
|
320
|
+
|
|
321
|
+
const lmsProbe = await probeLmStudioRuntime(lmsUrl, { timeoutMs: 2000, bypassCache: true }).catch(() => null);
|
|
322
|
+
|
|
323
|
+
let vllmProbe = { reachable: false, models: [], loadedModel: null };
|
|
324
|
+
try {
|
|
325
|
+
const root = vllmUrl.replace(/\/v1\/?$/, '').replace(/\/+$/, '');
|
|
326
|
+
const res = await fetch(`${root}/v1/models`, { signal: AbortSignal.timeout(2000) });
|
|
327
|
+
if (res.ok) {
|
|
328
|
+
const json = await res.json();
|
|
329
|
+
const modelList = Array.isArray(json?.data) ? json.data.map(m => m.id) : [];
|
|
330
|
+
vllmProbe = {
|
|
331
|
+
reachable: true,
|
|
332
|
+
models: modelList,
|
|
333
|
+
loadedModel: modelList[0] || null,
|
|
334
|
+
count: modelList.length,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
} catch {}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
engines: {
|
|
341
|
+
lms: {
|
|
342
|
+
id: 'lms',
|
|
343
|
+
name: lmsCompat.name,
|
|
344
|
+
targetLevel: lmsCompat.targetLevel,
|
|
345
|
+
supported: lmsCompat.supported,
|
|
346
|
+
compatibility: lmsCompat,
|
|
347
|
+
reachable: Boolean(lmsProbe?.reachable),
|
|
348
|
+
loadedModel: lmsProbe?.loadedModel || null,
|
|
349
|
+
loadedModels: lmsProbe?.loadedModels || [],
|
|
350
|
+
models: lmsProbe?.models || [],
|
|
351
|
+
port: 1234,
|
|
352
|
+
baseUrl: lmsUrl,
|
|
353
|
+
process: getBackgroundProcess('lmstudio'),
|
|
354
|
+
},
|
|
355
|
+
vllm: {
|
|
356
|
+
id: 'vllm',
|
|
357
|
+
name: vllmCompat.name,
|
|
358
|
+
targetLevel: vllmCompat.targetLevel,
|
|
359
|
+
supported: vllmCompat.supported,
|
|
360
|
+
compatibility: vllmCompat,
|
|
361
|
+
reachable: Boolean(vllmProbe.reachable),
|
|
362
|
+
loadedModel: vllmProbe.loadedModel,
|
|
363
|
+
models: vllmProbe.models,
|
|
364
|
+
port: 8000,
|
|
365
|
+
baseUrl: vllmUrl,
|
|
366
|
+
process: getBackgroundProcess('vllm'),
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
platform: detectPlatformAndWsl(),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
export function calculateAutoTensorSplit(gpuMetrics, reservedMainGpuGb = 3.5, mainGpuIndex = 0) {
|
|
375
|
+
const gpus = Array.isArray(gpuMetrics)
|
|
376
|
+
? gpuMetrics
|
|
377
|
+
: (Array.isArray(gpuMetrics?.gpus) ? gpuMetrics.gpus : []);
|
|
378
|
+
|
|
379
|
+
if (gpus.length < 2) {
|
|
380
|
+
const singleVram = gpus[0]
|
|
381
|
+
? (Number(gpus[0].freeGb) || (gpus[0].freeMb ? gpus[0].freeMb / 1024 : 0) || Number(gpus[0].totalGb) || 24)
|
|
382
|
+
: 24;
|
|
383
|
+
return {
|
|
384
|
+
applicable: false,
|
|
385
|
+
tensorSplit: '1',
|
|
386
|
+
ratios: [1],
|
|
387
|
+
usableGb: [Math.max(0.5, Number(Number(singleVram).toFixed(1)))],
|
|
388
|
+
reason: 'Requires at least 2 GPUs for multi-GPU tensor split',
|
|
389
|
+
toString() { return this.tensorSplit; },
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const usableGb = gpus.map((gpu, idx) => {
|
|
394
|
+
const free = Number(gpu.freeGb) || (gpu.freeMb ? gpu.freeMb / 1024 : 0);
|
|
395
|
+
const total = Number(gpu.totalGb) || (gpu.totalMb ? gpu.totalMb / 1024 : 0);
|
|
396
|
+
const rawVram = free > 0 ? free : (total > 0 ? total : 24);
|
|
397
|
+
const headroom = (idx === mainGpuIndex) ? reservedMainGpuGb : 0;
|
|
398
|
+
return Math.max(0.5, Number((rawVram - headroom).toFixed(2)));
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const ratios = usableGb.map(gb => Math.max(1, Math.round(gb)));
|
|
402
|
+
const tensorSplit = ratios.join(',');
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
applicable: true,
|
|
406
|
+
tensorSplit,
|
|
407
|
+
ratios,
|
|
408
|
+
usableGb,
|
|
409
|
+
mainGpu: mainGpuIndex,
|
|
410
|
+
reservedMainGpuGb,
|
|
411
|
+
toString() { return this.tensorSplit; },
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
export async function loadModelOnEngine(engine = 'lms', model, options = {}) {
|
|
417
|
+
const normEngine = String(engine).toLowerCase().trim();
|
|
418
|
+
|
|
419
|
+
if (normEngine === 'lms' || normEngine === 'lmstudio') {
|
|
420
|
+
const res = await wakeUpLmStudio({
|
|
421
|
+
baseUrl: options.baseUrl || 'http://127.0.0.1:1234/v1',
|
|
422
|
+
model,
|
|
423
|
+
quiet: options.quiet ?? false,
|
|
424
|
+
gpuOffload: options.gpuOffload,
|
|
425
|
+
cpuThreads: options.cpuThreads,
|
|
426
|
+
contextLength: options.contextLength,
|
|
427
|
+
splitMode: options.splitMode,
|
|
428
|
+
tensorSplit: options.tensorSplit,
|
|
429
|
+
mainGpu: options.mainGpu,
|
|
430
|
+
flashAttn: options.flashAttn,
|
|
431
|
+
batchSize: options.batchSize,
|
|
432
|
+
ubatchSize: options.ubatchSize,
|
|
433
|
+
htMode: options.htMode || options.hyperThreadingMode,
|
|
434
|
+
physicalCores: options.physicalCores,
|
|
435
|
+
hyperThreading: options.hyperThreading,
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
engine: 'lms',
|
|
439
|
+
...res,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (normEngine === 'vllm') {
|
|
444
|
+
const plan = buildVllmExecutionPlan(model, options);
|
|
445
|
+
if (!plan.ok) {
|
|
446
|
+
return {
|
|
447
|
+
ok: false,
|
|
448
|
+
engine: 'vllm',
|
|
449
|
+
reason: plan.reason,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const port = options.port || 8000;
|
|
454
|
+
const vllmUrl = options.baseUrl || `http://127.0.0.1:${port}/v1`;
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
const root = vllmUrl.replace(/\/v1\/?$/, '');
|
|
459
|
+
const ping = await fetch(`${root}/v1/models`, { signal: AbortSignal.timeout(1500) });
|
|
460
|
+
if (ping.ok) {
|
|
461
|
+
const json = await ping.json();
|
|
462
|
+
const models = (json?.data || []).map(m => m.id);
|
|
463
|
+
const hasModel = models.some(m => m === model || m.includes(model));
|
|
464
|
+
if (hasModel) {
|
|
465
|
+
return {
|
|
466
|
+
ok: true,
|
|
467
|
+
engine: 'vllm',
|
|
468
|
+
alreadyRunning: true,
|
|
469
|
+
modelLoaded: true,
|
|
470
|
+
loadedModel: model,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} catch {}
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const child = spawn(plan.binary, plan.args, {
|
|
479
|
+
detached: true,
|
|
480
|
+
stdio: 'ignore',
|
|
481
|
+
windowsHide: true,
|
|
482
|
+
});
|
|
483
|
+
if (child.pid) {
|
|
484
|
+
registerBackgroundProcess('vllm', child, {
|
|
485
|
+
command: plan.binary,
|
|
486
|
+
args: plan.args,
|
|
487
|
+
metadata: {
|
|
488
|
+
engine: 'vllm',
|
|
489
|
+
model,
|
|
490
|
+
isWsl: plan.isWsl,
|
|
491
|
+
distro: plan.distro,
|
|
492
|
+
port,
|
|
493
|
+
wslProcessPattern: 'vllm',
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
child.unref();
|
|
498
|
+
|
|
499
|
+
const maxWaitMs = options.maxWaitMs || 45000;
|
|
500
|
+
const start = Date.now();
|
|
501
|
+
while (Date.now() - start < maxWaitMs) {
|
|
502
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
503
|
+
try {
|
|
504
|
+
const root = vllmUrl.replace(/\/v1\/?$/, '');
|
|
505
|
+
const res = await fetch(`${root}/v1/models`, { signal: AbortSignal.timeout(1500) });
|
|
506
|
+
if (res.ok) {
|
|
507
|
+
const data = await res.json();
|
|
508
|
+
const models = (data?.data || []).map(m => m.id);
|
|
509
|
+
return {
|
|
510
|
+
ok: true,
|
|
511
|
+
engine: 'vllm',
|
|
512
|
+
awakened: true,
|
|
513
|
+
modelLoaded: true,
|
|
514
|
+
loadedModel: models[0] || model,
|
|
515
|
+
models,
|
|
516
|
+
pid: child.pid,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
} catch {}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
ok: false,
|
|
524
|
+
engine: 'vllm',
|
|
525
|
+
reason: `vLLM server started via ${plan.isWsl ? `WSL (${plan.distro})` : 'Linux'} but port ${port} did not respond within ${Math.round(maxWaitMs / 1000)}s`,
|
|
526
|
+
rawCommand: plan.rawCommand,
|
|
527
|
+
pid: child.pid,
|
|
528
|
+
};
|
|
529
|
+
} catch (err) {
|
|
530
|
+
return {
|
|
531
|
+
ok: false,
|
|
532
|
+
engine: 'vllm',
|
|
533
|
+
reason: `Failed to spawn vLLM: ${err.message}`,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
ok: false,
|
|
540
|
+
engine: normEngine,
|
|
541
|
+
reason: `Unsupported engine "${normEngine}"`,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export async function stopEngine(engine = 'vllm', options = {}) {
|
|
546
|
+
const normEngine = String(engine).toLowerCase().trim();
|
|
547
|
+
if (normEngine === 'vllm') {
|
|
548
|
+
const stopRes = stopBackgroundProcess('vllm', options);
|
|
549
|
+
const distro = options.distro || 'Ubuntu-26.04';
|
|
550
|
+
if (process.platform === 'win32') {
|
|
551
|
+
killWslProcessByName(distro, 'vllm');
|
|
552
|
+
}
|
|
553
|
+
return { ok: true, engine: 'vllm', stopped: stopRes.ok };
|
|
554
|
+
}
|
|
555
|
+
if (normEngine === 'lms' || normEngine === 'lmstudio') {
|
|
556
|
+
const stopRes1 = stopBackgroundProcess('lmstudio', options);
|
|
557
|
+
const stopRes2 = stopBackgroundProcess('lmstudio-server', options);
|
|
558
|
+
try {
|
|
559
|
+
const { resolveLmsCli } = await import('./lmStudioWakeup.js');
|
|
560
|
+
const lmsCmd = resolveLmsCli();
|
|
561
|
+
const { spawnSync } = await import('node:child_process');
|
|
562
|
+
spawnSync(lmsCmd, ['server', 'stop'], { stdio: 'ignore', windowsHide: true });
|
|
563
|
+
} catch {}
|
|
564
|
+
return { ok: true, engine: 'lms', stopped: stopRes1.ok || stopRes2.ok };
|
|
565
|
+
}
|
|
566
|
+
return { ok: false, reason: `Unknown engine "${engine}"` };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export async function unloadModelFromEngine(engine = 'lms', model = null, options = {}) {
|
|
570
|
+
const normEngine = String(engine).toLowerCase().trim();
|
|
571
|
+
if (normEngine === 'lms' || normEngine === 'lmstudio') {
|
|
572
|
+
return await unloadLmStudioModel(model, options);
|
|
573
|
+
}
|
|
574
|
+
if (normEngine === 'vllm') {
|
|
575
|
+
const stopRes = await stopEngine('vllm', options);
|
|
576
|
+
return {
|
|
577
|
+
ok: stopRes.ok,
|
|
578
|
+
unloaded: stopRes.ok,
|
|
579
|
+
engine: 'vllm',
|
|
580
|
+
model: model || 'all',
|
|
581
|
+
message: stopRes.ok ? 'vLLM server stopped and GPU VRAM freed' : 'Failed to stop vLLM server'
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return {
|
|
585
|
+
ok: false,
|
|
586
|
+
engine: normEngine,
|
|
587
|
+
message: `Unsupported engine "${normEngine}" for unload`
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export { buildLmsLoadArgs, loadLmStudioModel, unloadLmStudioModel, wakeUpLmStudio };
|
|
@@ -50,24 +50,14 @@ export function selectCases({ suite = DEFAULTS.suite, ids, limit } = {}) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export async function probeModel({ baseUrl, model }) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
response = await fetch(`${baseUrl.replace(/\/$/, '')}/models`, { signal: AbortSignal.timeout(8000) });
|
|
56
|
-
} catch {
|
|
53
|
+
const isLocal = baseUrl.includes('127.0.0.1') || baseUrl.includes('localhost');
|
|
54
|
+
if (isLocal) {
|
|
57
55
|
const { wakeUpLmStudio } = await import('../../../lmStudioWakeup.js');
|
|
58
56
|
await wakeUpLmStudio({ baseUrl, model });
|
|
59
|
-
response = await fetch(`${baseUrl.replace(/\/$/, '')}/models`, { signal: AbortSignal.timeout(8000) });
|
|
60
57
|
}
|
|
58
|
+
const response = await fetch(`${baseUrl.replace(/\/$/, '')}/models`, { signal: AbortSignal.timeout(8000) });
|
|
61
59
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
62
|
-
|
|
63
|
-
if (!models.data?.some(m => m.id === model)) {
|
|
64
|
-
const { wakeUpLmStudio } = await import('../../../lmStudioWakeup.js');
|
|
65
|
-
await wakeUpLmStudio({ baseUrl, model });
|
|
66
|
-
const retryRes = await fetch(`${baseUrl.replace(/\/$/, '')}/models`, { signal: AbortSignal.timeout(8000) });
|
|
67
|
-
if (retryRes.ok) {
|
|
68
|
-
models = await retryRes.json();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
60
|
+
const models = await response.json();
|
|
71
61
|
if (!models.data?.some(m => m.id === model)) throw new Error(`Model not advertised: ${model}`);
|
|
72
62
|
}
|
|
73
63
|
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { computeBenchmarkSummary } from '../reporting/benchmark.js';
|
|
2
2
|
const normalize = s => String(s).normalize('NFKC').toLowerCase().replace(/\s+/g, ' ');
|
|
3
3
|
|
|
4
|
+
function aliasMatches(text, alias) {
|
|
5
|
+
const normalizedText = normalize(text);
|
|
6
|
+
const normalizedAlias = normalize(alias);
|
|
7
|
+
if (!normalizedAlias) return false;
|
|
8
|
+
if (!/[\p{N}]/u.test(normalizedAlias)) return normalizedText.includes(normalizedAlias);
|
|
9
|
+
const timeMatch = normalizedAlias.match(/^(\d{1,2}):(\d{2})$/);
|
|
10
|
+
const escaped = timeMatch
|
|
11
|
+
? `0?${timeMatch[1]}:${timeMatch[2]}`
|
|
12
|
+
: normalizedAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
13
|
+
return new RegExp(`(?<![\\p{N}])${escaped}(?![\\p{N}])`, 'u').test(normalizedText);
|
|
14
|
+
}
|
|
15
|
+
|
|
4
16
|
function expandAnswerEquivalences(s) {
|
|
5
17
|
let text = String(s);
|
|
6
18
|
text = text.replace(/(?:พ\.ศ\.|พุทธศักราช)\s*(\d{4})/g, (match, beYear) => {
|
|
@@ -17,7 +29,7 @@ function expandAnswerEquivalences(s) {
|
|
|
17
29
|
export function scoreCoverage(c, answer) {
|
|
18
30
|
if (!c.groups.length) return { matched: 0, total: 0, allMatched: null, missing: [] };
|
|
19
31
|
const text = normalize(expandAnswerEquivalences(answer));
|
|
20
|
-
const missing = c.groups.filter(group => !group.some(alias => text
|
|
32
|
+
const missing = c.groups.filter(group => !group.some(alias => aliasMatches(text, alias)));
|
|
21
33
|
return { matched: c.groups.length - missing.length, total: c.groups.length, allMatched: missing.length === 0, missing };
|
|
22
34
|
}
|
|
23
35
|
|
|
@@ -191,10 +203,10 @@ export function checkGroundedness(c, answer, trace = []) {
|
|
|
191
203
|
let groundedInTools = 0;
|
|
192
204
|
|
|
193
205
|
for (const group of groups) {
|
|
194
|
-
const presentInAnswer = group.some(alias => text
|
|
206
|
+
const presentInAnswer = group.some(alias => aliasMatches(text, alias));
|
|
195
207
|
if (presentInAnswer) {
|
|
196
208
|
matchedInAnswer++;
|
|
197
|
-
const presentInTools = group.some(alias => toolOutputs
|
|
209
|
+
const presentInTools = group.some(alias => aliasMatches(toolOutputs, alias));
|
|
198
210
|
if (presentInTools) {
|
|
199
211
|
groundedInTools++;
|
|
200
212
|
}
|
|
@@ -14,6 +14,7 @@ import { renderBenchmarkPng } from '../reporting/benchmarkPng.js';
|
|
|
14
14
|
import { judgeReport } from '../analysis/judge.js';
|
|
15
15
|
import { acquireRunLock, createSaver, loadReportWithBackups } from '../core/store.js';
|
|
16
16
|
import { runSingleCase } from '../evaluation/worker.js';
|
|
17
|
+
import { evaluateGates, loadGatesFile } from '../analysis/gates.js';
|
|
17
18
|
|
|
18
19
|
let isRunning = false;
|
|
19
20
|
let activeAbortController = null;
|
|
@@ -421,7 +422,8 @@ export async function handleEvalApiRequest(req, res) {
|
|
|
421
422
|
'Pragma': 'no-cache',
|
|
422
423
|
'Expires': '0'
|
|
423
424
|
});
|
|
424
|
-
|
|
425
|
+
const gates = await loadGatesFile(DEFAULTS.gatesFile);
|
|
426
|
+
res.end(getEvalUiHtml(gates));
|
|
425
427
|
return true;
|
|
426
428
|
}
|
|
427
429
|
|
|
@@ -689,10 +691,8 @@ export async function handleEvalApiRequest(req, res) {
|
|
|
689
691
|
isRunning = false;
|
|
690
692
|
activeRunMeta = null;
|
|
691
693
|
activeCaseMeta = null;
|
|
692
|
-
const
|
|
693
|
-
const
|
|
694
|
-
const loopOk = (currentReport.summary?.loopFreeRate ?? 1) >= 0.95;
|
|
695
|
-
const gatesPassed = passAt1Ok && toolOk && loopOk;
|
|
694
|
+
const gates = await loadGatesFile(DEFAULTS.gatesFile);
|
|
695
|
+
const gatesPassed = evaluateGates(currentReport.summary || {}, gates).passed;
|
|
696
696
|
|
|
697
697
|
broadcastSSE('run_complete', {
|
|
698
698
|
report: currentReport,
|