kiku-agent 1.2.0 ā 1.3.0
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 +459 -407
- package/package.json +27 -3
- package/server/agent/agentCore.js +635 -267
- package/server/agent/appPaths.js +2 -0
- package/server/agent/catalogEntityResolver.js +629 -0
- package/server/agent/citations.js +0 -21
- package/server/agent/clarificationAnalytics.js +1 -1
- package/server/agent/clarifyingQuestions.js +286 -216
- package/server/agent/cli.js +528 -73
- package/server/agent/commandFeatures.js +123 -24
- package/server/agent/commandRegistry.js +128 -62
- package/server/agent/commands.js +1410 -95
- package/server/agent/config.js +253 -163
- package/server/agent/contextCompactor.js +239 -25
- package/server/agent/dynamicCatalog.js +192 -0
- package/server/agent/envBootstrap.js +46 -0
- package/server/agent/envManager.js +317 -0
- package/server/agent/eventBus.js +3 -3
- package/server/agent/feedbackLearning.js +0 -8
- package/server/agent/gpuMetrics.js +188 -55
- package/server/agent/ingestionWorker.js +339 -0
- package/server/agent/lanDiscovery.js +369 -0
- package/server/agent/llmClient.js +111 -39
- package/server/agent/lmStudioWakeup.js +144 -0
- package/server/agent/mcpClient.js +2 -2
- package/server/agent/memory.js +635 -494
- package/server/agent/modelConfig.js +487 -0
- package/server/agent/newsIntelligence.js +219 -0
- package/server/agent/newsWatch.js +85 -0
- package/server/agent/prompts.js +146 -0
- package/server/agent/publicTools.js +7 -0
- package/server/agent/queryDetails.js +62 -1
- package/server/agent/research.js +314 -35
- package/server/agent/routingInterceptor.js +77 -0
- package/server/agent/rumoredProjects.js +15 -0
- package/server/agent/sanitizer.js +117 -17
- package/server/agent/subagent.js +52 -9
- package/server/agent/suggestionTui.js +33 -13
- package/server/agent/suggestions.js +29 -13
- package/server/agent/telemetry.js +0 -11
- package/server/agent/terminalImage.js +0 -1
- package/server/agent/textToolProtocol.js +190 -0
- package/server/agent/tokenTracker.js +220 -0
- package/server/agent/toolRouter.js +362 -216
- package/server/agent/tools.js +1155 -194
- package/server/agent/ui.js +144 -41
- package/server/agent/vramService.js +572 -0
- package/server/agent/webSearch.js +2279 -273
- package/server/agent/youtubeAuth.js +82 -73
- package/server/agent/youtubeTranscript.js +209 -0
- package/server/apiRouter.js +383 -179
- package/server/mcp/data/brands.js +5 -5
- package/server/mcp/data/discography.js +56 -45
- package/server/mcp/data/gallery.js +9 -0
- package/server/mcp/data/index.js +1 -0
- package/server/mcp/data/industryIndex.js +251 -0
- package/server/mcp/data/profile.js +5 -5
- package/server/mcp/data/timeline.js +2 -2
- package/server/mcp/data/works.js +2 -2
- package/server/mcp/handler.js +0 -2
- package/server/mcp/prompts.js +42 -2
- package/server/mcp/resources.js +5 -1
- package/server/mcp/security.js +25 -14
- package/server/mcp/server.js +92 -3
- package/server/mcp/tools.js +20 -11
- package/server/web-chat/core/connection.js +218 -194
- package/server/web-chat/core/conversationContext.js +1 -1
- package/server/web-chat/core/directCatalogEngine.js +190 -184
- package/server/web-chat/core/followUpEngine.js +88 -11
- package/server/web-chat/core/security.js +7 -4
- package/server/web-chat/router.js +54 -17
- package/server/web-chat/routes/bilibiliRoute.js +34 -0
- package/server/web-chat/routes/chatRoute.js +488 -397
- package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
- package/server/web-chat/routes/exportRoute.js +2 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
- package/server/web-chat/routes/newsRoute.js +36 -6
- package/server/web-chat/routes/providerRoute.js +87 -0
- package/server/web-chat/routes/staticRoute.js +61 -13
- package/server/web-chat/routes/statusRoute.js +23 -2
- package/server/web-chat/routes/vramRoute.js +22 -0
- package/server/web-chat/server.js +135 -106
- package/server/web-chat/ui/htmlBuilder.js +8 -8
- package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
- package/server/web-chat/ui/scripts/client.js +10526 -8397
- package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
- package/server/web-chat/ui/styles/main.css +7471 -4246
- package/server/web-chat/ui/templates/body.html +305 -179
- package/src/data/brandsData.js +29 -6
- package/src/data/discographyData.js +918 -900
- package/src/data/galleryData.js +66 -0
- package/src/data/industryIndexData.js +292 -0
- package/src/data/musicVideoData.js +48 -5
- package/src/data/profileData.js +15 -1
- package/src/data/timelineData.js +4 -3
- package/src/data/worksData.js +536 -535
- package/src/lib/vram/estimator.js +464 -0
- package/src/lib/vram/modelRegistry.js +387 -0
- package/tokens.css +33 -2
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { config, CLOUD_PROVIDERS, normalizeProviderModel, matchModelIdentifier, extractModelQuantization, savePersistedProviderKey, savePersistedActiveProvider, savePersistedProviderModel, getPersistedActiveProvider } from './config.js';
|
|
1
|
+
import { config, CLOUD_PROVIDERS, normalizeProviderModel, matchModelIdentifier, extractModelQuantization, savePersistedProviderKey, savePersistedActiveProvider, savePersistedProviderModel, getPersistedActiveProvider, getProviderBaseUrl, savePersistedProviderBaseUrl, setActiveProvider } from './config.js';
|
|
2
2
|
import { parseApiErrorText } from './errors.js';
|
|
3
3
|
import { estimateTokens } from './telemetry.js';
|
|
4
|
+
import { estimateTokenCount } from './contextCompactor.js';
|
|
5
|
+
|
|
6
|
+
function computeRetryDelay(attempt, headers) {
|
|
7
|
+
const retryAfter = Number(headers?.get?.('retry-after'));
|
|
8
|
+
if (Number.isFinite(retryAfter) && retryAfter > 0) {
|
|
9
|
+
return Math.min(retryAfter * 1000, 15000) + Math.floor(Math.random() * 400);
|
|
10
|
+
}
|
|
11
|
+
return Math.min(8000, 1200 * 2 ** attempt) + Math.floor(Math.random() * 400);
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
export class LLMClient {
|
|
6
15
|
constructor(options = {}) {
|
|
@@ -12,7 +21,7 @@ export class LLMClient {
|
|
|
12
21
|
|
|
13
22
|
this.provider = providerId;
|
|
14
23
|
this.providerName = providerDef.name || providerId;
|
|
15
|
-
this.baseUrl = (options.baseUrl || providerDef.baseUrl || config.lmStudio.baseUrl).replace(/\/+$/, '');
|
|
24
|
+
this.baseUrl = (options.baseUrl || getProviderBaseUrl(providerId) || providerDef.baseUrl || config.lmStudio.baseUrl).replace(/\/+$/, '');
|
|
16
25
|
this.apiKey = options.apiKey !== undefined
|
|
17
26
|
? options.apiKey
|
|
18
27
|
: (providerDef.apiKey !== undefined ? providerDef.apiKey : (providerDef.isLocal ? (config.lmStudio.apiKey || 'lm-studio') : ''));
|
|
@@ -33,12 +42,21 @@ export class LLMClient {
|
|
|
33
42
|
|
|
34
43
|
this.provider = pId;
|
|
35
44
|
this.providerName = providerDef.name || pId;
|
|
36
|
-
this.baseUrl = (options.baseUrl || providerDef.baseUrl).replace(/\/+$/, '');
|
|
45
|
+
this.baseUrl = (options.baseUrl || getProviderBaseUrl(pId) || providerDef.baseUrl).replace(/\/+$/, '');
|
|
37
46
|
this.apiKey = options.apiKey !== undefined ? options.apiKey : (providerDef.apiKey || '');
|
|
38
47
|
this.model = normalizeProviderModel(pId, options.model || providerDef.defaultModel);
|
|
39
48
|
this.extraHeaders = { ...(providerDef.extraHeaders || {}), ...(options.extraHeaders || {}) };
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
setActiveProvider(pId, { model: this.model, baseUrl: options.baseUrl || null });
|
|
51
|
+
return this.getProviderInfo();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setBaseUrl(baseUrl, providerId = null) {
|
|
55
|
+
const targetProvider = String(providerId || this.provider).toLowerCase().trim();
|
|
56
|
+
const cleanUrl = savePersistedProviderBaseUrl(targetProvider, baseUrl);
|
|
57
|
+
if (targetProvider === this.provider) {
|
|
58
|
+
this.baseUrl = cleanUrl;
|
|
59
|
+
}
|
|
42
60
|
return this.getProviderInfo();
|
|
43
61
|
}
|
|
44
62
|
|
|
@@ -91,6 +109,8 @@ export class LLMClient {
|
|
|
91
109
|
}
|
|
92
110
|
} else if (this.provider === 'lmstudio') {
|
|
93
111
|
headers['Authorization'] = 'Bearer lm-studio';
|
|
112
|
+
} else if (this.provider === 'vllm') {
|
|
113
|
+
headers['Authorization'] = 'Bearer token-kiku';
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
return headers;
|
|
@@ -141,6 +161,9 @@ export class LLMClient {
|
|
|
141
161
|
} else if (res.status === 429) {
|
|
142
162
|
formattedError = `429 Rate Limit / Quota Exceeded: ${errSnippet}`;
|
|
143
163
|
}
|
|
164
|
+
if (def.platformNotice && process.platform === 'win32') {
|
|
165
|
+
formattedError += `\n Notice: ${def.platformNotice}`;
|
|
166
|
+
}
|
|
144
167
|
|
|
145
168
|
return {
|
|
146
169
|
connected: false,
|
|
@@ -168,16 +191,19 @@ export class LLMClient {
|
|
|
168
191
|
const exactMatch = models.find(m => m === this.model);
|
|
169
192
|
const fuzzyMatch = models.find(m => matchModelIdentifier(this.model, m));
|
|
170
193
|
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
194
|
+
|
|
195
|
+
const modelIsGemmaOrUnset = !this.model || /gemma/i.test(this.model);
|
|
196
|
+
const matchedGemma = modelIsGemmaOrUnset
|
|
197
|
+
? ((activeLoaded && /gemma/i.test(activeLoaded))
|
|
198
|
+
? activeLoaded
|
|
199
|
+
: (fuzzyMatch || models.find(m =>
|
|
200
|
+
m === 'google/gemma-4-e4b' ||
|
|
201
|
+
m === 'gemma-4-e4b' ||
|
|
202
|
+
m.toLowerCase().includes('gemma-4-e4b') ||
|
|
203
|
+
(m.toLowerCase().includes('gemma') && m.toLowerCase().includes('e4b')) ||
|
|
204
|
+
/gemma/i.test(m)
|
|
205
|
+
)))
|
|
206
|
+
: null;
|
|
181
207
|
|
|
182
208
|
const resolvedActive = exactMatch || (activeLoaded ? activeLoaded : (fuzzyMatch || matchedGemma || this.model));
|
|
183
209
|
|
|
@@ -214,32 +240,66 @@ export class LLMClient {
|
|
|
214
240
|
};
|
|
215
241
|
} catch (err) {
|
|
216
242
|
const latencyMs = Date.now() - startTime;
|
|
243
|
+
let errMsg = err.name === 'AbortError'
|
|
244
|
+
? `Connection timed out at ${this.baseUrl} (>8s)`
|
|
245
|
+
: `Cannot reach ${this.providerName} at ${this.baseUrl} (${err.message})`;
|
|
246
|
+
if (def.platformNotice && process.platform === 'win32') {
|
|
247
|
+
errMsg += `\n Notice: ${def.platformNotice}`;
|
|
248
|
+
}
|
|
217
249
|
return {
|
|
218
250
|
connected: false,
|
|
219
251
|
provider: this.provider,
|
|
220
252
|
providerName: this.providerName,
|
|
221
|
-
error:
|
|
222
|
-
? `Connection timed out at ${this.baseUrl} (>8s)`
|
|
223
|
-
: `Cannot reach ${this.providerName} at ${this.baseUrl} (${err.message})`,
|
|
253
|
+
error: errMsg,
|
|
224
254
|
models: [],
|
|
225
255
|
latencyMs
|
|
226
256
|
};
|
|
227
257
|
}
|
|
228
258
|
}
|
|
229
259
|
|
|
230
|
-
async
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
260
|
+
async healthCheck() {
|
|
261
|
+
return this.checkConnection();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
_isLegacyReasoningModel() {
|
|
265
|
+
return /^(o1|o3|deepseek-reasoner)/i.test(this.model) || this.model.includes('reasoner');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
toolsWillBeOmitted() {
|
|
270
|
+
return this._isLegacyReasoningModel();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async chat(firstArg, secondArg = {}) {
|
|
274
|
+
let messages, tools, temperature, maxTokens, stream, signal, onToken, onThought;
|
|
275
|
+
if (Array.isArray(firstArg)) {
|
|
276
|
+
messages = firstArg;
|
|
277
|
+
({
|
|
278
|
+
tools = null,
|
|
279
|
+
temperature = 0.3,
|
|
280
|
+
maxTokens = 8192,
|
|
281
|
+
stream = true,
|
|
282
|
+
signal = null,
|
|
283
|
+
onToken = null,
|
|
284
|
+
onThought = null
|
|
285
|
+
} = secondArg || {});
|
|
286
|
+
} else {
|
|
287
|
+
({
|
|
288
|
+
messages,
|
|
289
|
+
tools = null,
|
|
290
|
+
temperature = 0.3,
|
|
291
|
+
maxTokens = 8192,
|
|
292
|
+
stream = true,
|
|
293
|
+
signal = null,
|
|
294
|
+
onToken = null,
|
|
295
|
+
onThought = null
|
|
296
|
+
} = firstArg || {});
|
|
297
|
+
}
|
|
298
|
+
const isLegacyReasoningModel = this._isLegacyReasoningModel();
|
|
241
299
|
const usesCompletionTokenLimit = /^(o1|o3|gpt-5(?:\.|-|$))/i.test(this.model);
|
|
242
300
|
const rejectsTemperature = /^(o1|o3|gpt-5(?:\.|-|$))/i.test(this.model);
|
|
301
|
+
|
|
302
|
+
this.toolsDisabledReason = null;
|
|
243
303
|
|
|
244
304
|
const buildPayload = (includeTools = true, includeTemp = true) => {
|
|
245
305
|
const p = {
|
|
@@ -258,9 +318,13 @@ export class LLMClient {
|
|
|
258
318
|
p.max_tokens = maxTokens;
|
|
259
319
|
}
|
|
260
320
|
|
|
261
|
-
if (includeTools && tools && Array.isArray(tools) && tools.length > 0
|
|
262
|
-
|
|
263
|
-
|
|
321
|
+
if (includeTools && tools && Array.isArray(tools) && tools.length > 0) {
|
|
322
|
+
if (isLegacyReasoningModel) {
|
|
323
|
+
this.toolsDisabledReason = 'reasoning_model';
|
|
324
|
+
} else {
|
|
325
|
+
p.tools = tools;
|
|
326
|
+
p.tool_choice = "auto";
|
|
327
|
+
}
|
|
264
328
|
}
|
|
265
329
|
|
|
266
330
|
if (stream) {
|
|
@@ -278,7 +342,11 @@ export class LLMClient {
|
|
|
278
342
|
const abortHandler = () => controller.abort();
|
|
279
343
|
if (signal) signal.addEventListener('abort', abortHandler, { once: true });
|
|
280
344
|
|
|
345
|
+
let lastTimeoutReset = 0;
|
|
281
346
|
const resetTimeout = () => {
|
|
347
|
+
const now = Date.now();
|
|
348
|
+
if (now - lastTimeoutReset < 500) return;
|
|
349
|
+
lastTimeoutReset = now;
|
|
282
350
|
clearTimeout(timeoutId);
|
|
283
351
|
timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
284
352
|
};
|
|
@@ -308,6 +376,7 @@ export class LLMClient {
|
|
|
308
376
|
|
|
309
377
|
if ((lowerErr.includes('tool') || lowerErr.includes('function') || lowerErr.includes('schema')) && payload.tools) {
|
|
310
378
|
payload = buildPayload(false, true);
|
|
379
|
+
this.toolsDisabledReason = 'provider_rejected_schema';
|
|
311
380
|
retryNeeded = true;
|
|
312
381
|
} else if (lowerErr.includes('temperature') && payload.temperature !== undefined) {
|
|
313
382
|
payload = buildPayload(Boolean(payload.tools), false);
|
|
@@ -345,8 +414,7 @@ export class LLMClient {
|
|
|
345
414
|
}
|
|
346
415
|
|
|
347
416
|
if ([429, 500, 502, 503, 504].includes(res.status) && attempt < maxRetries && !controller.signal.aborted) {
|
|
348
|
-
|
|
349
|
-
await new Promise(r => setTimeout(r, backoffMs));
|
|
417
|
+
await new Promise(r => setTimeout(r, computeRetryDelay(attempt, res.headers)));
|
|
350
418
|
continue;
|
|
351
419
|
}
|
|
352
420
|
|
|
@@ -362,8 +430,7 @@ export class LLMClient {
|
|
|
362
430
|
if (controller.signal.aborted || attempt >= maxRetries || fetchErr.status) {
|
|
363
431
|
throw fetchErr;
|
|
364
432
|
}
|
|
365
|
-
|
|
366
|
-
await new Promise(r => setTimeout(r, backoffMs));
|
|
433
|
+
await new Promise(r => setTimeout(r, computeRetryDelay(attempt, null)));
|
|
367
434
|
}
|
|
368
435
|
}
|
|
369
436
|
|
|
@@ -388,8 +455,7 @@ export class LLMClient {
|
|
|
388
455
|
total_tokens: data.usage.total_tokens || ((data.usage.prompt_tokens || 0) + (data.usage.completion_tokens || 0))
|
|
389
456
|
};
|
|
390
457
|
} else {
|
|
391
|
-
const
|
|
392
|
-
const pTokens = estimateTokens(inputPrompt);
|
|
458
|
+
const pTokens = estimateTokenCount(messages);
|
|
393
459
|
const cTokens = estimateTokens(msg.content || '');
|
|
394
460
|
msg.usage = {
|
|
395
461
|
prompt_tokens: pTokens,
|
|
@@ -409,6 +475,7 @@ export class LLMClient {
|
|
|
409
475
|
let toolCalls = [];
|
|
410
476
|
let buffer = '';
|
|
411
477
|
let reportedUsage = null;
|
|
478
|
+
let malformedChunks = 0;
|
|
412
479
|
|
|
413
480
|
while (true) {
|
|
414
481
|
const { done, value } = await reader.read();
|
|
@@ -460,19 +527,24 @@ export class LLMClient {
|
|
|
460
527
|
}
|
|
461
528
|
}
|
|
462
529
|
} catch {
|
|
530
|
+
malformedChunks++;
|
|
463
531
|
}
|
|
464
532
|
}
|
|
465
533
|
}
|
|
466
534
|
|
|
467
535
|
clearTimeout(timeoutId);
|
|
468
536
|
|
|
537
|
+
if (malformedChunks > 0) {
|
|
538
|
+
|
|
539
|
+
console.warn(`[${this.providerName}] Dropped ${malformedChunks} malformed SSE chunk(s) during streaming.`);
|
|
540
|
+
}
|
|
541
|
+
|
|
469
542
|
let promptTokens = reportedUsage?.prompt_tokens ?? 0;
|
|
470
543
|
let completionTokens = reportedUsage?.completion_tokens ?? 0;
|
|
471
544
|
let reasoningTokens = reportedUsage?.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
472
545
|
|
|
473
546
|
if (!promptTokens) {
|
|
474
|
-
|
|
475
|
-
promptTokens = estimateTokens(inputPrompt);
|
|
547
|
+
promptTokens = estimateTokenCount(messages);
|
|
476
548
|
}
|
|
477
549
|
if (!completionTokens) {
|
|
478
550
|
completionTokens = estimateTokens(fullContent + (fullReasoning || ''));
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
|
|
6
|
+
export async function isLmStudioResponding(baseUrl = 'http://127.0.0.1:1234/v1', timeoutMs = 2500) {
|
|
7
|
+
try {
|
|
8
|
+
const root = baseUrl.replace(/\/v1\/?$/, '').replace(/\/+$/, '');
|
|
9
|
+
const res = await fetch(`${root}/v1/models`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
10
|
+
return res.ok;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function getLoadedModels(baseUrl = 'http://127.0.0.1:1234/v1', timeoutMs = 2500) {
|
|
17
|
+
try {
|
|
18
|
+
const root = baseUrl.replace(/\/v1\/?$/, '').replace(/\/+$/, '');
|
|
19
|
+
const res = await fetch(`${root}/v1/models`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
20
|
+
if (!res.ok) return [];
|
|
21
|
+
const data = await res.json();
|
|
22
|
+
return Array.isArray(data.data) ? data.data.map(m => m.id) : [];
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveLmsCli() {
|
|
29
|
+
const userHome = os.homedir();
|
|
30
|
+
const isWin = process.platform === 'win32';
|
|
31
|
+
const candidates = [
|
|
32
|
+
path.join(userHome, '.lmstudio', 'bin', isWin ? 'lms.exe' : 'lms'),
|
|
33
|
+
isWin ? 'lms.exe' : 'lms'
|
|
34
|
+
];
|
|
35
|
+
return candidates.find(c => {
|
|
36
|
+
try { return fs.existsSync(c); } catch { return false; }
|
|
37
|
+
}) || (isWin ? 'lms.exe' : 'lms');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function loadLmStudioModel(model, options = {}) {
|
|
41
|
+
const lmsCmd = resolveLmsCli();
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const args = ['load', model, '-y', '--identifier', model];
|
|
44
|
+
const child = spawn(lmsCmd, args, {
|
|
45
|
+
windowsHide: true,
|
|
46
|
+
stdio: 'pipe',
|
|
47
|
+
shell: process.platform === 'win32'
|
|
48
|
+
});
|
|
49
|
+
let output = '';
|
|
50
|
+
child.stdout?.on('data', d => { output += d.toString(); });
|
|
51
|
+
child.stderr?.on('data', d => { output += d.toString(); });
|
|
52
|
+
child.on('close', code => {
|
|
53
|
+
resolve({ ok: code === 0, output });
|
|
54
|
+
});
|
|
55
|
+
child.on('error', err => {
|
|
56
|
+
resolve({ ok: false, error: err.message });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function wakeUpLmStudio(options = {}) {
|
|
62
|
+
const {
|
|
63
|
+
baseUrl = 'http://127.0.0.1:1234/v1',
|
|
64
|
+
model = 'google/gemma-4-e4b',
|
|
65
|
+
maxWaitMs = 35000
|
|
66
|
+
} = options;
|
|
67
|
+
|
|
68
|
+
const isLocal = baseUrl.includes('127.0.0.1') || baseUrl.includes('localhost');
|
|
69
|
+
if (!isLocal) {
|
|
70
|
+
return { ok: false, reason: 'Non-local endpoint, automatic wakeup skipped' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
let serverUp = await isLmStudioResponding(baseUrl, 2000);
|
|
75
|
+
if (!serverUp) {
|
|
76
|
+
const userHome = os.homedir();
|
|
77
|
+
const possiblePaths = [
|
|
78
|
+
path.join(userHome, 'AppData', 'Local', 'Programs', 'LM Studio', 'LM Studio.exe'),
|
|
79
|
+
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'LM Studio', 'LM Studio.exe'),
|
|
80
|
+
'C:\\Program Files\\LM Studio\\LM Studio.exe'
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const appExe = possiblePaths.find(p => p && fs.existsSync(p));
|
|
84
|
+
if (appExe) {
|
|
85
|
+
try {
|
|
86
|
+
const child = spawn(appExe, [], {
|
|
87
|
+
detached: true,
|
|
88
|
+
stdio: 'ignore',
|
|
89
|
+
windowsHide: false
|
|
90
|
+
});
|
|
91
|
+
child.unref();
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const lmsCmd = resolveLmsCli();
|
|
97
|
+
const lmsChild = spawn(lmsCmd, ['server', 'start'], {
|
|
98
|
+
detached: true,
|
|
99
|
+
stdio: 'ignore',
|
|
100
|
+
windowsHide: true,
|
|
101
|
+
shell: process.platform === 'win32'
|
|
102
|
+
});
|
|
103
|
+
lmsChild.unref();
|
|
104
|
+
} catch {}
|
|
105
|
+
|
|
106
|
+
const start = Date.now();
|
|
107
|
+
while (Date.now() - start < maxWaitMs) {
|
|
108
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
109
|
+
if (await isLmStudioResponding(baseUrl, 2000)) {
|
|
110
|
+
serverUp = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!serverUp) {
|
|
117
|
+
return { ok: false, reason: 'Timed out waiting for LM Studio server' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if (model) {
|
|
122
|
+
const loaded = await getLoadedModels(baseUrl);
|
|
123
|
+
if (!loaded.includes(model)) {
|
|
124
|
+
try {
|
|
125
|
+
process.stdout.write(`\nšø [Kiku Auto-Wakeup] LM Studio server active. Auto-loading model "${model}" into VRAM...\n`);
|
|
126
|
+
} catch {}
|
|
127
|
+
await loadLmStudioModel(model);
|
|
128
|
+
|
|
129
|
+
const start = Date.now();
|
|
130
|
+
while (Date.now() - start < maxWaitMs) {
|
|
131
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
132
|
+
const currentModels = await getLoadedModels(baseUrl);
|
|
133
|
+
if (currentModels.includes(model)) {
|
|
134
|
+
try {
|
|
135
|
+
process.stdout.write(`šø [Kiku Auto-Wakeup] Model "${model}" loaded and ready!\n\n`);
|
|
136
|
+
} catch {}
|
|
137
|
+
return { ok: true, awakened: true, modelLoaded: true };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { ok: true, alreadyRunning: true };
|
|
144
|
+
}
|
|
@@ -185,7 +185,6 @@ export class McpClientManager {
|
|
|
185
185
|
let resources = serverConfig.resources || [];
|
|
186
186
|
let prompts = serverConfig.prompts || [];
|
|
187
187
|
|
|
188
|
-
// If tools not pre-provided, fetch once upon connection
|
|
189
188
|
if (!tools || tools.length === 0) {
|
|
190
189
|
try {
|
|
191
190
|
if (!this._isRateLimited(name)) {
|
|
@@ -570,7 +569,6 @@ export class McpClientManager {
|
|
|
570
569
|
if (cfg.enabled === false || cfg.disabled === true) continue;
|
|
571
570
|
if (this.servers.has(name)) continue;
|
|
572
571
|
|
|
573
|
-
// If tools are cached or it's a known Kiku remote MCP server, register lazily without network call on startup
|
|
574
572
|
let tools = cfg.tools;
|
|
575
573
|
if (!tools || tools.length === 0) {
|
|
576
574
|
if (name === 'kiku-mcp' || /jujingyi-kikuuu|vercel\.app/i.test(cfg.url || '')) {
|
|
@@ -641,3 +639,5 @@ export class McpClientManager {
|
|
|
641
639
|
return results;
|
|
642
640
|
}
|
|
643
641
|
}
|
|
642
|
+
|
|
643
|
+
export const mcpClientManager = new McpClientManager();
|