kiku-agent 1.3.2 → 1.3.4
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
package/server/agent/cli.js
CHANGED
|
@@ -6,6 +6,8 @@ import { stdin as input } from 'node:process';
|
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import './envBootstrap.js';
|
|
9
|
+
import { resolveDynamicWindowsAppPaths } from './appPaths.js';
|
|
10
|
+
import { registerBackgroundProcess } from './processManager.js';
|
|
9
11
|
import { KikuAgent } from './agentCore.js';
|
|
10
12
|
import { LLMClient, LMStudioClient } from './llmClient.js';
|
|
11
13
|
import { toolsSchema, toolHandlers, executeTool } from './tools.js';
|
|
@@ -116,6 +118,15 @@ function recordQueryDiagnostics(details) {
|
|
|
116
118
|
return lastQueryDetails;
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
export function resolveModelFallbackWarning(modelResolution = {}, opts = {}) {
|
|
122
|
+
if (!modelResolution.usedFallback) return null;
|
|
123
|
+
if (!opts.model) return null;
|
|
124
|
+
return {
|
|
125
|
+
requested: modelResolution.requestedModel || opts.model,
|
|
126
|
+
selected: modelResolution.selectedModel,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
119
130
|
export function parseCliArgs(customArgs = null) {
|
|
120
131
|
const args = customArgs || process.argv.slice(2);
|
|
121
132
|
const options = {
|
|
@@ -144,6 +155,9 @@ export function parseCliArgs(customArgs = null) {
|
|
|
144
155
|
eval: null,
|
|
145
156
|
hub: null,
|
|
146
157
|
portal: null,
|
|
158
|
+
gpuOffload: null,
|
|
159
|
+
cpuThreads: null,
|
|
160
|
+
nGpuLayers: null,
|
|
147
161
|
};
|
|
148
162
|
|
|
149
163
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -205,6 +219,20 @@ export function parseCliArgs(customArgs = null) {
|
|
|
205
219
|
else if (arg === '--portal') {
|
|
206
220
|
options.portal = (args[i + 1] && !args[i + 1].startsWith('-')) ? args[++i] : 'open';
|
|
207
221
|
}
|
|
222
|
+
else if (arg === '--n-gpu-layers' || arg === '--gpu-layers' || arg === '-ngl') {
|
|
223
|
+
const val = args[++i];
|
|
224
|
+
options.nGpuLayers = val;
|
|
225
|
+
options.gpuOffload = val;
|
|
226
|
+
}
|
|
227
|
+
else if (arg === '--gpu-offload') {
|
|
228
|
+
options.gpuOffload = args[++i];
|
|
229
|
+
}
|
|
230
|
+
else if (arg === '--threads' || arg === '--cpu-threads') {
|
|
231
|
+
const maybeThreads = Number(args[++i]);
|
|
232
|
+
if (Number.isFinite(maybeThreads) && maybeThreads >= 1) {
|
|
233
|
+
options.cpuThreads = Math.trunc(maybeThreads);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
208
236
|
else if (!arg.startsWith('-') && !options.query) { options.query = args.slice(i).join(' '); break; }
|
|
209
237
|
else if (arg.startsWith('-')) {
|
|
210
238
|
console.error(`Unknown option: ${arg}`);
|
|
@@ -243,11 +271,7 @@ async function attemptAutoStartLMStudio(baseUrl, model) {
|
|
|
243
271
|
const isMac = process.platform === 'darwin';
|
|
244
272
|
|
|
245
273
|
const candidates = isWin
|
|
246
|
-
?
|
|
247
|
-
`${process.env.LOCALAPPDATA}\\LM Studio\\LM Studio.exe`,
|
|
248
|
-
`${process.env.LOCALAPPDATA}\\Programs\\LM Studio\\LM Studio.exe`,
|
|
249
|
-
'C:\\Program Files\\LM Studio\\LM Studio.exe'
|
|
250
|
-
]
|
|
274
|
+
? resolveDynamicWindowsAppPaths('LM Studio', 'LM Studio.exe')
|
|
251
275
|
: isMac
|
|
252
276
|
? ['/Applications/LM Studio.app/Contents/MacOS/LM Studio']
|
|
253
277
|
: ['lmstudio', '/usr/local/bin/lmstudio', '/opt/lmstudio/bin/lmstudio'];
|
|
@@ -275,6 +299,13 @@ async function attemptAutoStartLMStudio(baseUrl, model) {
|
|
|
275
299
|
: ['serve', '--model', model, '--port', port],
|
|
276
300
|
{ detached: true, stdio: 'ignore', windowsHide: true }
|
|
277
301
|
);
|
|
302
|
+
if (child.pid) {
|
|
303
|
+
registerBackgroundProcess('lmstudio', child, {
|
|
304
|
+
command: exe,
|
|
305
|
+
args: isWin ? ['--model', model, '--serve', '--port', port] : ['serve', '--model', model, '--port', port],
|
|
306
|
+
metadata: { model, port },
|
|
307
|
+
});
|
|
308
|
+
}
|
|
278
309
|
child.unref();
|
|
279
310
|
|
|
280
311
|
return await waitForLMStudio(baseUrl, 45000);
|
|
@@ -638,6 +669,8 @@ export function printHelp(specificCmd = null) {
|
|
|
638
669
|
helpLines.push(` ${C.emerald}${"--web-chat, --web".padEnd(28)}${C.reset} ${C.white}Start standalone web chatbot (default port 3300)${C.reset}`);
|
|
639
670
|
helpLines.push(` ${C.emerald}${"--cn-tools, --cn".padEnd(28)}${C.reset} ${C.white}Enable Chinese web tools (search_bilibili, Baidu)${C.reset}`);
|
|
640
671
|
helpLines.push(` ${C.emerald}${"--port <n>".padEnd(28)}${C.reset} ${C.white}Web chatbot port 1-65535 (or --web-chat=<port>)${C.reset}`);
|
|
672
|
+
helpLines.push(` ${C.emerald}${"--n-gpu-layers <n>".padEnd(28)}${C.reset} ${C.white}GPU offload layers/mode (max, off, 0.0-1.0, or layer count)${C.reset}`);
|
|
673
|
+
helpLines.push(` ${C.emerald}${"--threads <n>".padEnd(28)}${C.reset} ${C.white}CPU thread pool size (1-256) for local model execution${C.reset}`);
|
|
641
674
|
helpLines.push(` ${C.emerald}${"--no-open, --open".padEnd(28)}${C.reset} ${C.white}Skip or force auto-open browser for --web-chat${C.reset}`);
|
|
642
675
|
helpLines.push(` ${C.emerald}${"-v, --verbose".padEnd(28)}${C.reset} ${C.white}Verbose logging and thinking${C.reset}`);
|
|
643
676
|
helpLines.push(` ${C.emerald}${"-V, --version".padEnd(28)}${C.reset} ${C.white}Print version${C.reset}`);
|
|
@@ -742,7 +775,7 @@ function printAd() {
|
|
|
742
775
|
|
|
743
776
|
function getInitialViewportLines() {
|
|
744
777
|
const heroLines = getWelcomeHeroLines();
|
|
745
|
-
const galleryLines = getWelcomeGalleryLines(2);
|
|
778
|
+
const galleryLines = process.env.KIKU_WELCOME_GALLERY === 'true' ? getWelcomeGalleryLines(2) : [];
|
|
746
779
|
return galleryLines.length > 0 ? [...heroLines, ...galleryLines] : heroLines;
|
|
747
780
|
}
|
|
748
781
|
|
|
@@ -768,6 +801,7 @@ class InteractiveTuiPrompt {
|
|
|
768
801
|
this.isResolving = false;
|
|
769
802
|
this.activeResolve = null;
|
|
770
803
|
this.activeReject = null;
|
|
804
|
+
this.parkedReads = [];
|
|
771
805
|
this.onCancel = options.onCancel || null;
|
|
772
806
|
this.onSigInt = options.onSigInt || null;
|
|
773
807
|
this._keyHandler = null;
|
|
@@ -844,6 +878,24 @@ class InteractiveTuiPrompt {
|
|
|
844
878
|
|
|
845
879
|
async readLine(options = {}) {
|
|
846
880
|
return new Promise((resolve, reject) => {
|
|
881
|
+
if (this.activeResolve && !this.isResolving) {
|
|
882
|
+
// A read is already active (the main input loop). Park it instead of
|
|
883
|
+
// leaking its promise: modal callers (/env init, /quiz, clarifying
|
|
884
|
+
// answers) own the terminal until they finish, then the parked read
|
|
885
|
+
// resumes via _resumeParked().
|
|
886
|
+
this.parkedReads.push({
|
|
887
|
+
resolve: this.activeResolve,
|
|
888
|
+
reject: this.activeReject,
|
|
889
|
+
line: this.line,
|
|
890
|
+
cursor: this.cursor,
|
|
891
|
+
promptText: this.promptText,
|
|
892
|
+
placeholder: this.inputPlaceholder,
|
|
893
|
+
cancelValue: this.cancelValue,
|
|
894
|
+
recordInputHistory: this.recordInputHistory,
|
|
895
|
+
});
|
|
896
|
+
this.activeResolve = null;
|
|
897
|
+
this.activeReject = null;
|
|
898
|
+
}
|
|
847
899
|
this._cleanupInput();
|
|
848
900
|
const inputOptions = typeof options === 'string' ? { prompt: options } : options;
|
|
849
901
|
this.promptText = inputOptions.prompt || this.defaultPromptText;
|
|
@@ -890,6 +942,38 @@ class InteractiveTuiPrompt {
|
|
|
890
942
|
this.activeResolve = null;
|
|
891
943
|
this.activeReject = null;
|
|
892
944
|
if (resolve) resolve(result);
|
|
945
|
+
this._resumeParked();
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
_resumeParked() {
|
|
949
|
+
const parked = this.parkedReads.pop();
|
|
950
|
+
if (!parked || this.isDisposed || this.activeResolve) return;
|
|
951
|
+
this.isResolving = false;
|
|
952
|
+
this.activeResolve = parked.resolve;
|
|
953
|
+
this.activeReject = parked.reject;
|
|
954
|
+
this.line = parked.line;
|
|
955
|
+
this.cursor = parked.cursor;
|
|
956
|
+
this.promptText = parked.promptText;
|
|
957
|
+
this.inputPlaceholder = parked.placeholder;
|
|
958
|
+
this.cancelValue = parked.cancelValue;
|
|
959
|
+
this.recordInputHistory = parked.recordInputHistory;
|
|
960
|
+
this.historyIndex = -1;
|
|
961
|
+
this.popoverIndex = 0;
|
|
962
|
+
this.popoverActive = false;
|
|
963
|
+
this.popoverItems = [];
|
|
964
|
+
this.ghostText = '';
|
|
965
|
+
try {
|
|
966
|
+
this.updateState();
|
|
967
|
+
readline.emitKeypressEvents(input);
|
|
968
|
+
if (input.isTTY) input.setRawMode(true);
|
|
969
|
+
this._keyHandler = (str, key) => {
|
|
970
|
+
try { this.handleKeypress(str, key); } catch (error) { this.fail(error); }
|
|
971
|
+
};
|
|
972
|
+
input.on('keypress', this._keyHandler);
|
|
973
|
+
this.render();
|
|
974
|
+
} catch (error) {
|
|
975
|
+
this.fail(error);
|
|
976
|
+
}
|
|
893
977
|
}
|
|
894
978
|
|
|
895
979
|
abortActiveRead(result = null) {
|
|
@@ -905,6 +989,7 @@ class InteractiveTuiPrompt {
|
|
|
905
989
|
this.activeResolve = null;
|
|
906
990
|
this.activeReject = null;
|
|
907
991
|
resolve(result);
|
|
992
|
+
this._resumeParked();
|
|
908
993
|
return true;
|
|
909
994
|
}
|
|
910
995
|
|
|
@@ -916,6 +1001,7 @@ class InteractiveTuiPrompt {
|
|
|
916
1001
|
this.activeResolve = null;
|
|
917
1002
|
this.activeReject = null;
|
|
918
1003
|
if (reject) reject(error);
|
|
1004
|
+
this._resumeParked();
|
|
919
1005
|
}
|
|
920
1006
|
|
|
921
1007
|
dispose() {
|
|
@@ -925,6 +1011,10 @@ class InteractiveTuiPrompt {
|
|
|
925
1011
|
this.activeResolve = null;
|
|
926
1012
|
this.activeReject = null;
|
|
927
1013
|
this.isResolving = true;
|
|
1014
|
+
const parked = this.parkedReads.splice(0);
|
|
1015
|
+
for (const entry of parked) {
|
|
1016
|
+
try { entry.resolve(null); } catch { }
|
|
1017
|
+
}
|
|
928
1018
|
}
|
|
929
1019
|
|
|
930
1020
|
updateState() {
|
|
@@ -1712,6 +1802,10 @@ async function executeQueryWithUI(agent, prompt, isThinkingVisible = false, sign
|
|
|
1712
1802
|
console.log(formatQuestionsForCLI(questions, C));
|
|
1713
1803
|
},
|
|
1714
1804
|
onClarifyingAnswer: (queryId) => {
|
|
1805
|
+
if (globalTuiPrompt && typeof globalTuiPrompt.readLine === 'function') {
|
|
1806
|
+
return globalTuiPrompt.readLine({ prompt: `${C.gold}> ${C.reset}`, history: false, cancelValue: '' })
|
|
1807
|
+
.then(answer => String(answer ?? '').trim());
|
|
1808
|
+
}
|
|
1715
1809
|
return new Promise((resolve) => {
|
|
1716
1810
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1717
1811
|
rl.question(`${C.gold}> ${C.reset}`, (answer) => {
|
|
@@ -1926,7 +2020,7 @@ function vramZoneTag(zone) {
|
|
|
1926
2020
|
* model's estimated footprint, so users immediately see whether the loaded
|
|
1927
2021
|
* model is a heavy fit for their card. Informational only — never blocks.
|
|
1928
2022
|
*/
|
|
1929
|
-
async function printResourceVerificationPanel(client) {
|
|
2023
|
+
async function printResourceVerificationPanel(client, { verbose = false } = {}) {
|
|
1930
2024
|
if (!client || client.provider !== 'lmstudio') return;
|
|
1931
2025
|
try {
|
|
1932
2026
|
const [hw, runtime] = await Promise.all([
|
|
@@ -1950,12 +2044,44 @@ async function printResourceVerificationPanel(client) {
|
|
|
1950
2044
|
contextLength: runtimeCtx,
|
|
1951
2045
|
});
|
|
1952
2046
|
|
|
1953
|
-
const lines = [];
|
|
1954
2047
|
let livePct = null;
|
|
1955
2048
|
if (hw.available && hw.freeBytes !== null && hw.freeBytes !== undefined) {
|
|
1956
2049
|
const usedBytes = Math.max(0, hw.totalBytes - hw.freeBytes);
|
|
1957
2050
|
const live = classifyAllocation({ totalBytes: usedBytes, availableBytes: hw.totalBytes });
|
|
1958
2051
|
livePct = live.usagePct;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
let verdict = null;
|
|
2055
|
+
if (livePct !== null) {
|
|
2056
|
+
if (livePct <= 80) verdict = `${C.emerald}Resource load is comfortable (${livePct}% VRAM in use).${C.reset}`;
|
|
2057
|
+
else if (livePct <= 95) verdict = `${C.gold}This model is heavy for the card (${livePct}% VRAM in use) — expect slower prefill on long contexts.${C.reset}`;
|
|
2058
|
+
else verdict = `${C.roseGlow}VRAM is critically full (${livePct}%) — lower the context or switch to a smaller quant in LM Studio.${C.reset}`;
|
|
2059
|
+
} else if (estimate.totalBytes > 0 && hw.available) {
|
|
2060
|
+
const estCls = classifyAllocation({ totalBytes: estimate.totalBytes, availableBytes: hw.totalBytes });
|
|
2061
|
+
if (estCls.zone === 'RED') verdict = `${C.gold}Naive estimate exceeds the card — if it loads anyway, sliding-window caching is saving you; otherwise lower ctx/quant.${C.reset}`;
|
|
2062
|
+
else verdict = `${C.dim}Live usage unavailable — estimate says ${estCls.zone}.${C.reset}`;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
if (!verbose) {
|
|
2066
|
+
const gpuDesc = hw.available && hw.freeBytes !== null
|
|
2067
|
+
? `${hw.name} (${livePct !== null ? `${livePct}% VRAM used` : 'active'}, ${formatGb(hw.freeBytes)} free)`
|
|
2068
|
+
: (hw.available ? hw.name : 'CPU / RAM');
|
|
2069
|
+
const cleanVerdict = verdict ? stripAnsi(verdict).replace(/\s*\([^)]*\)/, '') : 'Ready';
|
|
2070
|
+
printRichCard({
|
|
2071
|
+
title: 'Hardware & Inference Target',
|
|
2072
|
+
status: livePct && livePct > 95 ? 'warning' : 'connected',
|
|
2073
|
+
lines: [
|
|
2074
|
+
`• GPU: ${gpuDesc} • Model: ${client.model}`,
|
|
2075
|
+
`• Memory: ${cleanVerdict} (use /vram for full telemetry)`,
|
|
2076
|
+
],
|
|
2077
|
+
});
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
const lines = [];
|
|
2082
|
+
if (hw.available && hw.freeBytes !== null && hw.freeBytes !== undefined) {
|
|
2083
|
+
const usedBytes = Math.max(0, hw.totalBytes - hw.freeBytes);
|
|
2084
|
+
const live = classifyAllocation({ totalBytes: usedBytes, availableBytes: hw.totalBytes });
|
|
1959
2085
|
lines.push(`${C.gold}GPU (live):${C.reset} ${C.white}${hw.name}${C.reset} — ${C.bold}${live.usagePct}%${C.reset} ${C.dim}(${formatGb(usedBytes)} / ${formatGb(hw.totalBytes)} used)${C.reset} ${vramZoneTag(live.zone)}`);
|
|
1960
2086
|
lines.push(`${C.gold}Free VRAM:${C.reset} ${C.emerald}${formatGb(hw.freeBytes)}${C.reset}`);
|
|
1961
2087
|
} else if (hw.available) {
|
|
@@ -1975,16 +2101,6 @@ async function printResourceVerificationPanel(client) {
|
|
|
1975
2101
|
(estCls && estCls.zone !== 'UNKNOWN' ? ` ${vramZoneTag(estCls.zone)}` : ''));
|
|
1976
2102
|
}
|
|
1977
2103
|
|
|
1978
|
-
let verdict = null;
|
|
1979
|
-
if (livePct !== null) {
|
|
1980
|
-
if (livePct <= 80) verdict = `${C.emerald}Resource load is comfortable (${livePct}% VRAM in use).${C.reset}`;
|
|
1981
|
-
else if (livePct <= 95) verdict = `${C.gold}This model is heavy for the card (${livePct}% VRAM in use) — expect slower prefill on long contexts.${C.reset}`;
|
|
1982
|
-
else verdict = `${C.roseGlow}VRAM is critically full (${livePct}%) — lower the context or switch to a smaller quant in LM Studio.${C.reset}`;
|
|
1983
|
-
} else if (estimate.totalBytes > 0 && hw.available) {
|
|
1984
|
-
const estCls = classifyAllocation({ totalBytes: estimate.totalBytes, availableBytes: hw.totalBytes });
|
|
1985
|
-
if (estCls.zone === 'RED') verdict = `${C.gold}Naive estimate exceeds the card — if it loads anyway, sliding-window caching is saving you; otherwise lower ctx/quant.${C.reset}`;
|
|
1986
|
-
else verdict = `${C.dim}Live usage unavailable — estimate says ${estCls.zone}.${C.reset}`;
|
|
1987
|
-
}
|
|
1988
2104
|
if (verdict) lines.push(`${C.gold}Verdict:${C.reset} ${verdict}`);
|
|
1989
2105
|
lines.push(`${C.dim}Details: /vram live • /vram compare • /vram ctx${C.reset}`);
|
|
1990
2106
|
|
|
@@ -2072,7 +2188,7 @@ async function executeHeadlessQuery(opts, query) {
|
|
|
2072
2188
|
let executionError = null;
|
|
2073
2189
|
|
|
2074
2190
|
try {
|
|
2075
|
-
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
2191
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model, strictModel: Boolean(opts.model) });
|
|
2076
2192
|
const modelResolution = await modelManager.resolve({ requestedModel: opts.model || client.model });
|
|
2077
2193
|
|
|
2078
2194
|
if (!modelResolution.connected || !modelResolution.selectedModel) {
|
|
@@ -2322,8 +2438,13 @@ async function main() {
|
|
|
2322
2438
|
apiKey: opts.apiKey,
|
|
2323
2439
|
model: opts.model
|
|
2324
2440
|
});
|
|
2325
|
-
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
2326
|
-
const modelResolution = await modelManager.resolve({
|
|
2441
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model, strictModel: Boolean(opts.model) });
|
|
2442
|
+
const modelResolution = await modelManager.resolve({
|
|
2443
|
+
requestedModel: opts.model || client.model,
|
|
2444
|
+
autoWakeup: true,
|
|
2445
|
+
gpuOffload: opts.gpuOffload,
|
|
2446
|
+
cpuThreads: opts.cpuThreads,
|
|
2447
|
+
});
|
|
2327
2448
|
if (!modelResolution.connected || !modelResolution.selectedModel) {
|
|
2328
2449
|
console.error(modelResolution.error || `No available chat model found for ${client.providerName}`);
|
|
2329
2450
|
process.exit(2);
|
|
@@ -2429,24 +2550,15 @@ async function main() {
|
|
|
2429
2550
|
}
|
|
2430
2551
|
|
|
2431
2552
|
if (!health.connected) {
|
|
2432
|
-
printRichCard({
|
|
2433
|
-
title: `${client.providerName} Offline`,
|
|
2434
|
-
status: 'offline',
|
|
2435
|
-
lines: [
|
|
2436
|
-
`• Endpoint: ${baseUrl || 'local'} (${health.error || 'unreachable'})`,
|
|
2437
|
-
...(client.provider === 'lmstudio' ? ['• Action: Attempting auto-start of local LM Studio...'] : []),
|
|
2438
|
-
],
|
|
2439
|
-
});
|
|
2440
|
-
|
|
2441
2553
|
if (client.provider === 'lmstudio') {
|
|
2442
2554
|
const autoHealth = await attemptAutoStartLMStudio(baseUrl, opts.model || client.model);
|
|
2443
2555
|
if (autoHealth) {
|
|
2444
2556
|
health = autoHealth;
|
|
2445
2557
|
printRichCard({
|
|
2446
|
-
title: 'LM Studio Started',
|
|
2447
|
-
status: '
|
|
2558
|
+
title: 'LM Studio Auto-Started',
|
|
2559
|
+
status: 'connected',
|
|
2448
2560
|
lines: [
|
|
2449
|
-
'Local LM Studio process started and responding.',
|
|
2561
|
+
'• Local LM Studio process started and responding.',
|
|
2450
2562
|
],
|
|
2451
2563
|
});
|
|
2452
2564
|
} else {
|
|
@@ -2456,9 +2568,9 @@ async function main() {
|
|
|
2456
2568
|
title: 'LM Studio Offline • AI Chat Locked',
|
|
2457
2569
|
status: 'offline',
|
|
2458
2570
|
lines: [
|
|
2459
|
-
|
|
2460
|
-
'Open LM Studio and load
|
|
2461
|
-
'Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit',
|
|
2571
|
+
`• Endpoint: ${baseUrl || 'http://127.0.0.1:1234/v1'} (unreachable)`,
|
|
2572
|
+
'• Open LM Studio and load google/gemma-4-e4b — auto-connects when loaded in VRAM.',
|
|
2573
|
+
'• Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit',
|
|
2462
2574
|
],
|
|
2463
2575
|
});
|
|
2464
2576
|
}
|
|
@@ -2478,10 +2590,15 @@ async function main() {
|
|
|
2478
2590
|
}
|
|
2479
2591
|
}
|
|
2480
2592
|
|
|
2481
|
-
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
2593
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model, strictModel: Boolean(opts.model) });
|
|
2482
2594
|
const modelResolution = catalogOnlyMode
|
|
2483
2595
|
? { connected: false, state: 'catalog_only' }
|
|
2484
|
-
: await modelManager.resolve({
|
|
2596
|
+
: await modelManager.resolve({
|
|
2597
|
+
requestedModel: opts.model || client.model,
|
|
2598
|
+
autoWakeup: true,
|
|
2599
|
+
gpuOffload: opts.gpuOffload,
|
|
2600
|
+
cpuThreads: opts.cpuThreads,
|
|
2601
|
+
});
|
|
2485
2602
|
if (!catalogOnlyMode && (!modelResolution.connected || !modelResolution.selectedModel)) {
|
|
2486
2603
|
printRichCard({
|
|
2487
2604
|
title: 'Model Not Loaded • AI Chat Locked',
|
|
@@ -2505,13 +2622,14 @@ async function main() {
|
|
|
2505
2622
|
],
|
|
2506
2623
|
});
|
|
2507
2624
|
}
|
|
2508
|
-
|
|
2625
|
+
const fallbackWarning = resolveModelFallbackWarning(modelResolution, opts);
|
|
2626
|
+
if (fallbackWarning) {
|
|
2509
2627
|
printRichCard({
|
|
2510
2628
|
title: 'Model Fallback',
|
|
2511
2629
|
status: 'warning',
|
|
2512
2630
|
lines: [
|
|
2513
|
-
`Requested model ${
|
|
2514
|
-
`Fallback model ${
|
|
2631
|
+
`Requested model ${fallbackWarning.requested} was unavailable.`,
|
|
2632
|
+
`Fallback model ${fallbackWarning.selected} selected.`,
|
|
2515
2633
|
],
|
|
2516
2634
|
});
|
|
2517
2635
|
}
|
|
@@ -2571,12 +2689,18 @@ async function main() {
|
|
|
2571
2689
|
}
|
|
2572
2690
|
|
|
2573
2691
|
if (!catalogOnlyMode) {
|
|
2574
|
-
await printResourceVerificationPanel(client);
|
|
2692
|
+
await printResourceVerificationPanel(client, { verbose: opts.verbose });
|
|
2575
2693
|
}
|
|
2576
2694
|
|
|
2577
2695
|
if (agent.restoredMessageCount > 0) {
|
|
2578
2696
|
const s = agent.session;
|
|
2579
|
-
|
|
2697
|
+
printRichCard({
|
|
2698
|
+
title: 'Active Session Restored',
|
|
2699
|
+
status: 'session',
|
|
2700
|
+
lines: [
|
|
2701
|
+
`• Session: ${s.id} — ${s.messages.length} message(s) loaded from history`,
|
|
2702
|
+
],
|
|
2703
|
+
});
|
|
2580
2704
|
}
|
|
2581
2705
|
|
|
2582
2706
|
agent.stateMachine.on('enter:' + AgentStates.FETCHING_TOOL, ({ toolName }) => {
|
|
@@ -2631,7 +2755,7 @@ async function main() {
|
|
|
2631
2755
|
? await modelManager.resolve()
|
|
2632
2756
|
: await client.checkConnection();
|
|
2633
2757
|
|
|
2634
|
-
if (resolution?.connected && (resolution.selectedModel || resolution.activeLoadedModel)) {
|
|
2758
|
+
if (resolution?.connected && resolution.isModelLoaded !== false && (resolution.selectedModel || resolution.activeLoadedModel)) {
|
|
2635
2759
|
const detectedModel = resolution.selectedModel || resolution.activeLoadedModel;
|
|
2636
2760
|
catalogOnlyMode = false;
|
|
2637
2761
|
tuiPrompt.isConnected = true;
|
|
@@ -2749,7 +2873,7 @@ async function main() {
|
|
|
2749
2873
|
if (catalogOnlyMode && modelManager) {
|
|
2750
2874
|
try {
|
|
2751
2875
|
const autoProbe = await modelManager.resolve();
|
|
2752
|
-
if (autoProbe?.connected && (autoProbe.selectedModel || autoProbe.activeLoadedModel)) {
|
|
2876
|
+
if (autoProbe?.connected && autoProbe.isModelLoaded !== false && (autoProbe.selectedModel || autoProbe.activeLoadedModel)) {
|
|
2753
2877
|
cmdCtx.setCatalogOnlyMode(false);
|
|
2754
2878
|
if (tuiPrompt) {
|
|
2755
2879
|
tuiPrompt.modelName = autoProbe.selectedModel || autoProbe.activeLoadedModel;
|
|
@@ -2835,6 +2959,7 @@ async function main() {
|
|
|
2835
2959
|
'/config', '/settings', '/conf',
|
|
2836
2960
|
'/sources', '/src', '/srcs',
|
|
2837
2961
|
'/queue',
|
|
2962
|
+
'/unload',
|
|
2838
2963
|
'/ads', '/sponsor', '/ad',
|
|
2839
2964
|
]);
|
|
2840
2965
|
|
|
@@ -2853,7 +2978,7 @@ async function main() {
|
|
|
2853
2978
|
if (base === '/session' && (!sub || ['info', 'list', 'summary'].includes(sub))) return true;
|
|
2854
2979
|
if (base === '/lang' && !sub) return true;
|
|
2855
2980
|
if (base === '/thinking' && !sub) return true;
|
|
2856
|
-
if (base === '/model' && (!sub ||
|
|
2981
|
+
if (base === '/model' && (!sub || ['status', 'list', 'recommend', 'unload', 'engine', 'config', 'params', 'preset', 'presets', 'files', 'gguf', 'temp', 'temperature', 'topp', 'ctx', 'context', 'thinking'].includes(sub))) return true;
|
|
2857
2982
|
if (base === '/provider' && (!sub || sub === 'list' || sub === 'status' || sub === 'info')) return true;
|
|
2858
2983
|
if (base === '/mcp' && (!sub || sub === 'status' || sub === 'list' || sub === 'tools')) return true;
|
|
2859
2984
|
if (base === '/env' && (!sub || sub === 'get' || sub === 'path')) return true;
|
|
@@ -3051,7 +3176,7 @@ async function main() {
|
|
|
3051
3176
|
const autoProbe = modelManager
|
|
3052
3177
|
? await modelManager.resolve()
|
|
3053
3178
|
: await client.checkConnection();
|
|
3054
|
-
if (autoProbe?.connected && (autoProbe.selectedModel || autoProbe.activeLoadedModel)) {
|
|
3179
|
+
if (autoProbe?.connected && autoProbe.isModelLoaded !== false && (autoProbe.selectedModel || autoProbe.activeLoadedModel)) {
|
|
3055
3180
|
catalogOnlyMode = false;
|
|
3056
3181
|
tuiPrompt.isConnected = true;
|
|
3057
3182
|
tuiPrompt.modelName = autoProbe.selectedModel || autoProbe.activeLoadedModel;
|
|
@@ -3108,6 +3233,7 @@ async function main() {
|
|
|
3108
3233
|
|
|
3109
3234
|
lastQuery = trimmed;
|
|
3110
3235
|
queryQueue.push({ id: 'q_' + Date.now(), number: queryNumber, prompt: trimmed, addedAt: Date.now() });
|
|
3236
|
+
queryCount++;
|
|
3111
3237
|
triggerQueueProcessing();
|
|
3112
3238
|
}
|
|
3113
3239
|
|
|
@@ -3116,7 +3242,10 @@ async function main() {
|
|
|
3116
3242
|
globalTuiPrompt.dispose();
|
|
3117
3243
|
globalTuiPrompt = null;
|
|
3118
3244
|
}
|
|
3119
|
-
exitAlternateScreen();
|
|
3245
|
+
exitAlternateScreen();
|
|
3246
|
+
if (queryCount === 0 && !process.stdin.isTTY) {
|
|
3247
|
+
console.log(`\n ${C.dim}Tip: Run 'kiku-agent' directly on its own line. Avoid chaining with ';' in PowerShell (e.g. 'cmd1 ; kiku-agent') as PowerShell pipes empty input into stdin.${C.reset}`);
|
|
3248
|
+
}
|
|
3120
3249
|
process.stdout.write(`\n ${C.sakura}Goodbye • 再见 (鞠躬 ~ 谢谢!)${C.reset}\n\n`, () => {
|
|
3121
3250
|
process.exit(0);
|
|
3122
3251
|
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export function toAnthropicMessages(messages = []) {
|
|
2
|
+
const system = [];
|
|
3
|
+
const converted = [];
|
|
4
|
+
for (const message of messages) {
|
|
5
|
+
const role = message?.role;
|
|
6
|
+
if (role === 'system') {
|
|
7
|
+
if (message.content) system.push(String(message.content));
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
if (role === 'tool') {
|
|
11
|
+
const toolUseId = message.tool_call_id || message.tool_use_id || 'unknown_tool_call';
|
|
12
|
+
const rawContent = String(message.content ?? '');
|
|
13
|
+
let isError = false;
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(rawContent);
|
|
16
|
+
if (parsed && (parsed.status === 'error' || parsed.error || parsed.is_error)) {
|
|
17
|
+
isError = true;
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
|
|
21
|
+
const toolResultBlock = {
|
|
22
|
+
type: 'tool_result',
|
|
23
|
+
tool_use_id: toolUseId,
|
|
24
|
+
content: rawContent,
|
|
25
|
+
...(isError ? { is_error: true } : {}),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const lastMsg = converted[converted.length - 1];
|
|
30
|
+
if (lastMsg && lastMsg.role === 'user' && Array.isArray(lastMsg.content) && lastMsg.content[0]?.type === 'tool_result') {
|
|
31
|
+
lastMsg.content.push(toolResultBlock);
|
|
32
|
+
} else {
|
|
33
|
+
converted.push({
|
|
34
|
+
role: 'user',
|
|
35
|
+
content: [toolResultBlock],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (role === 'assistant') {
|
|
41
|
+
const blocks = [];
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(message.thinking_blocks) && message.thinking_blocks.length > 0) {
|
|
44
|
+
blocks.push(...message.thinking_blocks);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const textContent = normalizeContent(message?.content);
|
|
48
|
+
if (textContent) {
|
|
49
|
+
blocks.push({ type: 'text', text: textContent });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
53
|
+
for (const call of message.tool_calls) {
|
|
54
|
+
blocks.push({
|
|
55
|
+
type: 'tool_use',
|
|
56
|
+
id: call.id || `call_${Date.now()}`,
|
|
57
|
+
name: call.function?.name || call.name || '',
|
|
58
|
+
input: parseJson(call.function?.arguments ?? call.arguments),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (blocks.length > 0) {
|
|
63
|
+
converted.push({ role: 'assistant', content: blocks });
|
|
64
|
+
} else {
|
|
65
|
+
converted.push({ role: 'assistant', content: textContent || '' });
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
converted.push({ role: role === 'assistant' ? 'assistant' : 'user', content: normalizeContent(message?.content) });
|
|
70
|
+
}
|
|
71
|
+
return { system: system.join('\n\n'), messages: converted };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function toAnthropicTools(tools = []) {
|
|
75
|
+
return (tools || []).map(tool => ({
|
|
76
|
+
name: tool.function?.name || tool.name,
|
|
77
|
+
description: tool.function?.description || tool.description || '',
|
|
78
|
+
input_schema: tool.function?.parameters || tool.parameters || { type: 'object', properties: {} },
|
|
79
|
+
})).filter(tool => tool.name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function normalizeAnthropicResponse(data = {}) {
|
|
83
|
+
const blocks = Array.isArray(data.content) ? data.content : [];
|
|
84
|
+
const text = blocks.filter(block => block.type === 'text').map(block => block.text || '').join('');
|
|
85
|
+
const reasoning = blocks.filter(block => block.type === 'thinking').map(block => block.thinking || '').join('');
|
|
86
|
+
const thinkingBlocks = blocks.filter(block => block.type === 'thinking' || block.type === 'redacted_thinking');
|
|
87
|
+
const toolCalls = blocks.filter(block => block.type === 'tool_use').map(block => ({
|
|
88
|
+
id: block.id || `call_${Date.now()}`,
|
|
89
|
+
type: 'function',
|
|
90
|
+
function: { name: block.name || '', arguments: JSON.stringify(block.input || {}) },
|
|
91
|
+
}));
|
|
92
|
+
return {
|
|
93
|
+
role: 'assistant',
|
|
94
|
+
content: text,
|
|
95
|
+
...(reasoning ? { reasoning_content: reasoning } : {}),
|
|
96
|
+
...(thinkingBlocks.length ? { thinking_blocks: thinkingBlocks } : {}),
|
|
97
|
+
...(toolCalls.length ? { tool_calls: toolCalls } : {}),
|
|
98
|
+
usage: data.usage ? {
|
|
99
|
+
prompt_tokens: data.usage.input_tokens || 0,
|
|
100
|
+
completion_tokens: data.usage.output_tokens || 0,
|
|
101
|
+
reasoning_tokens: 0,
|
|
102
|
+
total_tokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0),
|
|
103
|
+
} : undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeContent(content) {
|
|
108
|
+
if (Array.isArray(content)) return content.map(part => part?.text || '').join('');
|
|
109
|
+
return content == null ? '' : String(content);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseJson(value) {
|
|
113
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) return value;
|
|
114
|
+
try { return JSON.parse(String(value || '{}')); } catch { return {}; }
|
|
115
|
+
}
|
|
116
|
+
|