kiku-agent 1.3.1 → 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.
Files changed (232) hide show
  1. package/README.md +69 -12
  2. package/package.json +5 -12
  3. package/server/agent/agentCore.js +319 -46
  4. package/server/agent/appPaths.js +31 -0
  5. package/server/agent/audioPlayer.js +339 -12
  6. package/server/agent/autoContext.js +193 -0
  7. package/server/agent/catalogEntityResolver.js +170 -28
  8. package/server/agent/clarifyingQuestions.js +137 -35
  9. package/server/agent/cli.js +177 -48
  10. package/server/agent/cloudAdapters.js +116 -0
  11. package/server/agent/commandRegistry.js +409 -80
  12. package/server/agent/commands.js +1056 -125
  13. package/server/agent/config.js +1088 -690
  14. package/server/agent/contextCompactor.js +213 -21
  15. package/server/agent/cpuMetrics.js +199 -0
  16. package/server/agent/engineService.js +591 -0
  17. package/server/agent/eval/AGENTS.md +228 -0
  18. package/server/agent/eval/analyze.py +684 -0
  19. package/server/agent/eval/gates.json +15 -0
  20. package/server/agent/eval/modules/analysis/compare.js +198 -0
  21. package/server/agent/eval/modules/analysis/gates.js +72 -0
  22. package/server/agent/eval/modules/analysis/judge.js +70 -0
  23. package/server/agent/eval/modules/core/cli.js +453 -0
  24. package/server/agent/eval/modules/core/config.js +60 -0
  25. package/server/agent/eval/modules/core/store.js +236 -0
  26. package/server/agent/eval/modules/evaluation/cases.js +689 -0
  27. package/server/agent/eval/modules/evaluation/runner.js +288 -0
  28. package/server/agent/eval/modules/evaluation/scoring.js +363 -0
  29. package/server/agent/eval/modules/evaluation/worker.js +122 -0
  30. package/server/agent/eval/modules/reporting/benchmark.js +265 -0
  31. package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
  32. package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
  33. package/server/agent/eval/modules/server/evalApi.js +1099 -0
  34. package/server/agent/eval/modules/ui/evalUi.js +4799 -0
  35. package/server/agent/eval/modules/utils/smokes.js +119 -0
  36. package/server/agent/eventBus.js +1 -0
  37. package/server/agent/ink/main.jsx +196 -191
  38. package/server/agent/jobManager.js +11 -1
  39. package/server/agent/lanDiscovery.js +721 -23
  40. package/server/agent/llmClient.js +317 -71
  41. package/server/agent/lmStudioWakeup.js +517 -65
  42. package/server/agent/locationService.js +797 -0
  43. package/server/agent/mcpQuizData.js +509 -0
  44. package/server/agent/memory/contextAllocator.js +19 -7
  45. package/server/agent/memory.js +3 -1
  46. package/server/agent/modelCompatibility.js +352 -0
  47. package/server/agent/modelDefaults.js +1 -0
  48. package/server/agent/modelManager.js +46 -2
  49. package/server/agent/newsWatch.js +9 -3
  50. package/server/agent/processManager.js +229 -0
  51. package/server/agent/prompts.js +174 -8
  52. package/server/agent/routingInterceptor.js +9 -1
  53. package/server/agent/sanitizer.js +41 -6
  54. package/server/agent/searchQueryPlanner.js +202 -0
  55. package/server/agent/session.js +59 -4
  56. package/server/agent/tests/agentBubbleSystem.test.js +65 -0
  57. package/server/agent/tests/agentFollowUps.test.js +253 -0
  58. package/server/agent/tests/agentMusicControl.test.js +245 -0
  59. package/server/agent/tests/apiRouter.test.js +108 -0
  60. package/server/agent/tests/architectureFixes.test.js +81 -0
  61. package/server/agent/tests/architectureRefactoring.test.js +194 -0
  62. package/server/agent/tests/autoContext.test.js +176 -0
  63. package/server/agent/tests/autoResearch.test.js +64 -0
  64. package/server/agent/tests/bilibiliEmbed.test.js +296 -0
  65. package/server/agent/tests/catalogSync.test.js +130 -0
  66. package/server/agent/tests/chatHardening.test.js +121 -0
  67. package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
  68. package/server/agent/tests/citations.test.js +180 -0
  69. package/server/agent/tests/clarificationAnalytics.test.js +219 -0
  70. package/server/agent/tests/clarifyingQuestions.test.js +627 -0
  71. package/server/agent/tests/cliAutomation.test.js +158 -0
  72. package/server/agent/tests/cloudProviders.test.js +322 -0
  73. package/server/agent/tests/commandInput.test.js +24 -0
  74. package/server/agent/tests/commands.test.js +506 -0
  75. package/server/agent/tests/contextIntelligence.test.js +209 -0
  76. package/server/agent/tests/conversationSafety.test.js +53 -0
  77. package/server/agent/tests/cpuMetrics.test.js +101 -0
  78. package/server/agent/tests/decisionAndSearchEnhancements.test.js +167 -0
  79. package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
  80. package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
  81. package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
  82. package/server/agent/tests/edgeCasesScenarios.test.js +131 -0
  83. package/server/agent/tests/engineService.test.js +436 -0
  84. package/server/agent/tests/engineUnload.test.js +94 -0
  85. package/server/agent/tests/entertainmentMetrics.test.js +126 -0
  86. package/server/agent/tests/envBootstrap.test.js +87 -0
  87. package/server/agent/tests/envCommand.test.js +284 -0
  88. package/server/agent/tests/errorHandling.test.js +80 -0
  89. package/server/agent/tests/errors.test.js +13 -0
  90. package/server/agent/tests/evalAnalyze.test.js +54 -0
  91. package/server/agent/tests/evalCommand.test.js +54 -0
  92. package/server/agent/tests/evalCorpus.test.js +126 -0
  93. package/server/agent/tests/evalHarness.test.js +62 -0
  94. package/server/agent/tests/evalJudgeContext.test.js +38 -0
  95. package/server/agent/tests/evalMetrics.test.js +320 -0
  96. package/server/agent/tests/evalStore.test.js +190 -0
  97. package/server/agent/tests/evalStudio.test.js +179 -0
  98. package/server/agent/tests/feedbackLearning.test.js +186 -0
  99. package/server/agent/tests/fetchApi.test.js +99 -0
  100. package/server/agent/tests/firstRun.test.js +44 -0
  101. package/server/agent/tests/gemma4eOptimization.test.js +95 -0
  102. package/server/agent/tests/hardwareTiers.test.js +206 -0
  103. package/server/agent/tests/hybridWebSearch.test.js +194 -0
  104. package/server/agent/tests/judgeService.test.js +251 -0
  105. package/server/agent/tests/lanDiscovery.test.js +324 -0
  106. package/server/agent/tests/lanInterfaces.test.js +510 -0
  107. package/server/agent/tests/lmStudioWakeup.test.js +278 -0
  108. package/server/agent/tests/locationTool.test.js +278 -0
  109. package/server/agent/tests/mcpClient.test.js +201 -0
  110. package/server/agent/tests/memoryRoute.test.js +173 -0
  111. package/server/agent/tests/memoryUiNormalization.test.js +75 -0
  112. package/server/agent/tests/modelCompatibility.test.js +148 -0
  113. package/server/agent/tests/modelManager.test.js +141 -0
  114. package/server/agent/tests/multilingualScenarios.test.js +69 -0
  115. package/server/agent/tests/musicControlUiBridge.test.js +113 -0
  116. package/server/agent/tests/newCommands.test.js +347 -0
  117. package/server/agent/tests/newCommandsTui.test.js +141 -0
  118. package/server/agent/tests/newTools.test.js +113 -0
  119. package/server/agent/tests/newsIntelligence.test.js +178 -0
  120. package/server/agent/tests/nextGenArchitecture.test.js +101 -0
  121. package/server/agent/tests/offlineInput.test.js +217 -0
  122. package/server/agent/tests/openDomainPrompt.test.js +197 -0
  123. package/server/agent/tests/openDomainSearch.test.js +121 -0
  124. package/server/agent/tests/packagingGuard.test.js +57 -0
  125. package/server/agent/tests/presentation.test.js +61 -0
  126. package/server/agent/tests/processManager.test.js +144 -0
  127. package/server/agent/tests/providerPersistence.test.js +44 -0
  128. package/server/agent/tests/queryBenchmarks.test.js +83 -0
  129. package/server/agent/tests/queryDetails.test.js +126 -0
  130. package/server/agent/tests/quickActionsAndModals.test.js +180 -0
  131. package/server/agent/tests/realWorldValidation.test.js +391 -0
  132. package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
  133. package/server/agent/tests/runtimeWatchdog.test.js +32 -0
  134. package/server/agent/tests/sanitizer.test.js +37 -0
  135. package/server/agent/tests/scenariosData.js +378 -0
  136. package/server/agent/tests/schemaRouting.test.js +54 -0
  137. package/server/agent/tests/searchQueryPlanner.test.js +108 -0
  138. package/server/agent/tests/securityAuditRemediation.test.js +221 -0
  139. package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
  140. package/server/agent/tests/securityHardening.test.js +146 -0
  141. package/server/agent/tests/securitySanitizer.test.js +38 -0
  142. package/server/agent/tests/sessionAndCopy.test.js +267 -0
  143. package/server/agent/tests/sidebarControls.test.js +135 -0
  144. package/server/agent/tests/statefulMemory.test.js +277 -0
  145. package/server/agent/tests/subagent.test.js +216 -0
  146. package/server/agent/tests/subagentProgress.test.js +109 -0
  147. package/server/agent/tests/subagentTextFallback.test.js +115 -0
  148. package/server/agent/tests/suggestions.test.js +199 -0
  149. package/server/agent/tests/suggestionsTui.test.js +251 -0
  150. package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
  151. package/server/agent/tests/terminalImage.test.js +80 -0
  152. package/server/agent/tests/terminalMarkdown.test.js +166 -0
  153. package/server/agent/tests/terminalText.test.js +133 -0
  154. package/server/agent/tests/textProtocolLoop.test.js +197 -0
  155. package/server/agent/tests/textToolParsing.test.js +187 -0
  156. package/server/agent/tests/theme.test.js +26 -0
  157. package/server/agent/tests/toolRegistryParity.test.js +53 -0
  158. package/server/agent/tests/toolRoutingRegressions.test.js +160 -0
  159. package/server/agent/tests/tools.test.js +259 -0
  160. package/server/agent/tests/unifiedEndpoints.test.js +159 -0
  161. package/server/agent/tests/v110Features.test.js +262 -0
  162. package/server/agent/tests/versionBump.test.js +69 -0
  163. package/server/agent/tests/vramEstimator.test.js +595 -0
  164. package/server/agent/tests/webChatDocs.test.js +199 -0
  165. package/server/agent/tests/webChatInferenceControl.test.js +298 -0
  166. package/server/agent/tests/webChatInspection.test.js +551 -0
  167. package/server/agent/tests/webChatMcp.test.js +94 -0
  168. package/server/agent/tests/webChatMemory.test.js +112 -0
  169. package/server/agent/tests/webChatSecurity.test.js +169 -0
  170. package/server/agent/tests/webExtract.test.js +284 -0
  171. package/server/agent/tests/webSources.test.js +281 -0
  172. package/server/agent/textToolProtocol.js +173 -46
  173. package/server/agent/toolRouter.js +318 -44
  174. package/server/agent/tools.js +877 -96
  175. package/server/agent/ui.js +39 -34
  176. package/server/agent/vramService.js +35 -54
  177. package/server/agent/webSearch.js +441 -162
  178. package/server/agent/websearch/orchestrator.js +15 -5
  179. package/server/agent/websearch/resilience/cache.js +9 -2
  180. package/server/agent/websearch/security/ssrf.js +41 -10
  181. package/server/agent/youtubeAuth.js +5 -20
  182. package/server/apiRouter.js +5558 -4422
  183. package/server/eval/judgeRubrics.js +110 -0
  184. package/server/eval/judgeService.js +290 -0
  185. package/server/eval/runBenchmark.js +182 -0
  186. package/server/mcp/data/discography.js +2 -2
  187. package/server/mcp/examples/antigravity.example.json +17 -0
  188. package/server/mcp/examples/librechat.example.yaml +16 -0
  189. package/server/mcp/handler.js +8 -4
  190. package/server/mcp/security.js +220 -21
  191. package/server/mcp/server.js +2 -1
  192. package/server/mcp/tests/connection.test.js +162 -0
  193. package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
  194. package/server/mcp/tools.js +7 -1
  195. package/server/supabase_migration_prefix.sql +102 -0
  196. package/server/supabase_schema.sql +63 -0
  197. package/server/web-chat/core/connection.js +179 -218
  198. package/server/web-chat/core/conversationContext.js +9 -2
  199. package/server/web-chat/core/directCatalogEngine.js +52 -28
  200. package/server/web-chat/core/dramaKnowledge.js +33 -22
  201. package/server/web-chat/core/followUpEngine.js +735 -81
  202. package/server/web-chat/core/futureEventGuard.js +5 -1
  203. package/server/web-chat/core/musicControlAction.js +70 -0
  204. package/server/web-chat/core/security.js +32 -21
  205. package/server/web-chat/core/toolInspector.js +6 -6
  206. package/server/web-chat/core/urlGuard.js +45 -0
  207. package/server/web-chat/router.js +204 -157
  208. package/server/web-chat/routes/chatRoute.js +885 -488
  209. package/server/web-chat/routes/engineRoute.js +270 -0
  210. package/server/web-chat/routes/exportRoute.js +1 -1
  211. package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
  212. package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
  213. package/server/web-chat/routes/mcpRoute.js +386 -0
  214. package/server/web-chat/routes/memoryRoute.js +253 -0
  215. package/server/web-chat/routes/providerRoute.js +49 -16
  216. package/server/web-chat/routes/staticRoute.js +306 -17
  217. package/server/web-chat/routes/statusRoute.js +194 -73
  218. package/server/web-chat/routes/suggestRoute.js +11 -3
  219. package/server/web-chat/routes/verifyRoute.js +26 -4
  220. package/server/web-chat/server.js +7 -6
  221. package/server/web-chat/ui/docsBuilder.js +3558 -0
  222. package/server/web-chat/ui/htmlBuilder.js +12 -0
  223. package/server/web-chat/ui/scripts/client.js +3401 -1302
  224. package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
  225. package/server/web-chat/ui/settingsBuilder.js +7870 -0
  226. package/server/web-chat/ui/styles/lan-card.css +165 -0
  227. package/server/web-chat/ui/styles/main.css +4289 -447
  228. package/server/web-chat/ui/sw.js +64 -0
  229. package/server/web-chat/ui/templates/body.html +407 -281
  230. package/src/data/awardsData.js +146 -146
  231. package/src/data/musicVideoData.js +6 -4
  232. 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 };