kiku-agent 1.3.0 → 1.3.2
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 +7 -7
- package/package.json +4 -7
- package/server/agent/agentCore.js +2 -2
- package/server/agent/catalogEntityResolver.js +8 -0
- package/server/agent/cli.js +4 -0
- package/server/agent/eval/AGENTS.md +228 -0
- package/server/agent/eval/analyze.py +684 -0
- package/server/agent/eval/gates.json +15 -0
- package/server/agent/eval/modules/analysis/compare.js +198 -0
- package/server/agent/eval/modules/analysis/gates.js +72 -0
- package/server/agent/eval/modules/analysis/judge.js +70 -0
- package/server/agent/eval/modules/core/cli.js +453 -0
- package/server/agent/eval/modules/core/config.js +60 -0
- package/server/agent/eval/modules/core/store.js +236 -0
- package/server/agent/eval/modules/evaluation/cases.js +689 -0
- package/server/agent/eval/modules/evaluation/runner.js +298 -0
- package/server/agent/eval/modules/evaluation/scoring.js +351 -0
- package/server/agent/eval/modules/evaluation/worker.js +122 -0
- package/server/agent/eval/modules/reporting/benchmark.js +265 -0
- package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
- package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
- package/server/agent/eval/modules/server/evalApi.js +1099 -0
- package/server/agent/eval/modules/ui/evalUi.js +4796 -0
- package/server/agent/eval/modules/utils/smokes.js +119 -0
- package/server/agent/ink/App.jsx +348 -0
- package/server/agent/ink/Panel.jsx +54 -0
- package/server/agent/ink/PromptInput.jsx +321 -0
- package/server/agent/ink/TaskPlanPanel.jsx +88 -0
- package/server/agent/ink/ThinkingPanel.jsx +68 -0
- package/server/agent/ink/ToolResult.jsx +133 -0
- package/server/agent/ink/main.jsx +195 -0
- package/server/agent/ink/useEventBus.js +218 -0
- package/server/agent/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/prompts.js +1 -1
- package/server/agent/telemetry/hardwareTiers.js +332 -0
- package/server/agent/telemetry/runtimeWatchdog.js +66 -0
- package/server/agent/telemetry/techPowerUpClient.js +682 -0
- package/server/agent/telemetry/techpowerup-mcp.json +22 -0
- package/server/agent/tests/agentBubbleSystem.test.js +65 -0
- package/server/agent/tests/agentFollowUps.test.js +127 -0
- package/server/agent/tests/apiRouter.test.js +108 -0
- package/server/agent/tests/architectureFixes.test.js +81 -0
- package/server/agent/tests/architectureRefactoring.test.js +194 -0
- package/server/agent/tests/autoResearch.test.js +64 -0
- package/server/agent/tests/bilibiliEmbed.test.js +251 -0
- package/server/agent/tests/catalogSync.test.js +130 -0
- package/server/agent/tests/citations.test.js +180 -0
- package/server/agent/tests/clarificationAnalytics.test.js +219 -0
- package/server/agent/tests/clarifyingQuestions.test.js +594 -0
- package/server/agent/tests/cliAutomation.test.js +121 -0
- package/server/agent/tests/cloudProviders.test.js +234 -0
- package/server/agent/tests/commandInput.test.js +24 -0
- package/server/agent/tests/commands.test.js +417 -0
- package/server/agent/tests/contextIntelligence.test.js +162 -0
- package/server/agent/tests/conversationSafety.test.js +53 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +135 -0
- package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
- package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
- package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +49 -0
- package/server/agent/tests/entertainmentMetrics.test.js +126 -0
- package/server/agent/tests/envBootstrap.test.js +87 -0
- package/server/agent/tests/envCommand.test.js +215 -0
- package/server/agent/tests/errorHandling.test.js +80 -0
- package/server/agent/tests/errors.test.js +13 -0
- package/server/agent/tests/evalAnalyze.test.js +54 -0
- package/server/agent/tests/evalCommand.test.js +54 -0
- package/server/agent/tests/evalCorpus.test.js +126 -0
- package/server/agent/tests/evalHarness.test.js +62 -0
- package/server/agent/tests/evalJudgeContext.test.js +38 -0
- package/server/agent/tests/evalMetrics.test.js +312 -0
- package/server/agent/tests/evalStore.test.js +190 -0
- package/server/agent/tests/evalStudio.test.js +179 -0
- package/server/agent/tests/feedbackLearning.test.js +186 -0
- package/server/agent/tests/firstRun.test.js +44 -0
- package/server/agent/tests/gemma4eOptimization.test.js +95 -0
- package/server/agent/tests/hardwareTiers.test.js +206 -0
- package/server/agent/tests/hybridWebSearch.test.js +192 -0
- package/server/agent/tests/judgeService.test.js +251 -0
- package/server/agent/tests/lanDiscovery.test.js +319 -0
- package/server/agent/tests/mcpClient.test.js +201 -0
- package/server/agent/tests/modelManager.test.js +109 -0
- package/server/agent/tests/multilingualScenarios.test.js +69 -0
- package/server/agent/tests/newCommands.test.js +347 -0
- package/server/agent/tests/newCommandsTui.test.js +141 -0
- package/server/agent/tests/newTools.test.js +113 -0
- package/server/agent/tests/newsIntelligence.test.js +178 -0
- package/server/agent/tests/nextGenArchitecture.test.js +99 -0
- package/server/agent/tests/offlineInput.test.js +217 -0
- package/server/agent/tests/packagingGuard.test.js +57 -0
- package/server/agent/tests/presentation.test.js +61 -0
- package/server/agent/tests/providerPersistence.test.js +39 -0
- package/server/agent/tests/queryBenchmarks.test.js +83 -0
- package/server/agent/tests/queryDetails.test.js +126 -0
- package/server/agent/tests/realWorldValidation.test.js +391 -0
- package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
- package/server/agent/tests/runtimeWatchdog.test.js +32 -0
- package/server/agent/tests/sanitizer.test.js +37 -0
- package/server/agent/tests/scenariosData.js +378 -0
- package/server/agent/tests/schemaRouting.test.js +54 -0
- package/server/agent/tests/securityAuditRemediation.test.js +214 -0
- package/server/agent/tests/securityHardening.test.js +146 -0
- package/server/agent/tests/securitySanitizer.test.js +38 -0
- package/server/agent/tests/sessionAndCopy.test.js +232 -0
- package/server/agent/tests/sidebarControls.test.js +101 -0
- package/server/agent/tests/statefulMemory.test.js +270 -0
- package/server/agent/tests/subagent.test.js +216 -0
- package/server/agent/tests/subagentProgress.test.js +109 -0
- package/server/agent/tests/subagentTextFallback.test.js +115 -0
- package/server/agent/tests/suggestions.test.js +199 -0
- package/server/agent/tests/suggestionsTui.test.js +251 -0
- package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
- package/server/agent/tests/terminalImage.test.js +80 -0
- package/server/agent/tests/terminalMarkdown.test.js +166 -0
- package/server/agent/tests/terminalText.test.js +133 -0
- package/server/agent/tests/textProtocolLoop.test.js +126 -0
- package/server/agent/tests/textToolParsing.test.js +139 -0
- package/server/agent/tests/theme.test.js +26 -0
- package/server/agent/tests/toolRegistryParity.test.js +53 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +119 -0
- package/server/agent/tests/tools.test.js +259 -0
- package/server/agent/tests/unifiedEndpoints.test.js +152 -0
- package/server/agent/tests/v110Features.test.js +262 -0
- package/server/agent/tests/versionBump.test.js +69 -0
- package/server/agent/tests/vramEstimator.test.js +520 -0
- package/server/agent/tests/webChatInspection.test.js +300 -0
- package/server/agent/tests/webChatMemory.test.js +112 -0
- package/server/agent/tests/webChatSecurity.test.js +169 -0
- package/server/agent/tests/webExtract.test.js +261 -0
- package/server/agent/tests/webSources.test.js +247 -0
- package/server/agent/websearch/adapters/duckduckgo.js +97 -0
- package/server/agent/websearch/adapters/jina.js +77 -0
- package/server/agent/websearch/adapters/searxng.js +57 -0
- package/server/agent/websearch/core/canonical.js +89 -0
- package/server/agent/websearch/core/fusion.js +81 -0
- package/server/agent/websearch/core/types.js +29 -0
- package/server/agent/websearch/extract.js +605 -0
- package/server/agent/websearch/index.js +43 -0
- package/server/agent/websearch/orchestrator.js +266 -0
- package/server/agent/websearch/resilience/breaker.js +97 -0
- package/server/agent/websearch/resilience/cache.js +59 -0
- package/server/agent/websearch/resilience/limiter.js +93 -0
- package/server/agent/websearch/security/filter.js +36 -0
- package/server/agent/websearch/security/ssrf.js +289 -0
- package/server/agent/websearch/telemetry.js +71 -0
- package/server/apiRouter.js +7 -11
- package/server/eval/judgeRubrics.js +110 -0
- package/server/eval/judgeService.js +290 -0
- package/server/eval/runBenchmark.js +182 -0
- package/server/mcp/examples/librechat.example.yaml +16 -0
- package/server/mcp/tests/connection.test.js +156 -0
- package/server/routes/agent.js +55 -0
- package/server/supabase_schema.sql +63 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import { McpClientManager } from '../mcpClient.js';
|
|
9
|
+
import { KikuAgent } from '../agentCore.js';
|
|
10
|
+
import { routeCommand } from '../commands.js';
|
|
11
|
+
import { PACKAGE_VERSION } from '../version.js';
|
|
12
|
+
|
|
13
|
+
const stdioServerPath = fileURLToPath(new URL('../../mcp/stdio.js', import.meta.url));
|
|
14
|
+
|
|
15
|
+
test('McpClientManager connects over stdio, discovers tools, and executes tool call', { timeout: 30_000 }, async () => {
|
|
16
|
+
const manager = new McpClientManager({ clientInfo: { name: 'kiku-agent-test', version: PACKAGE_VERSION }, autoLoad: false });
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const connResult = await manager.connect('local_kiku', {
|
|
20
|
+
command: process.execPath,
|
|
21
|
+
args: [stdioServerPath],
|
|
22
|
+
cwd: process.cwd(),
|
|
23
|
+
}, { persist: false });
|
|
24
|
+
|
|
25
|
+
assert.equal(connResult.success, true);
|
|
26
|
+
assert.equal(connResult.serverName, 'local_kiku');
|
|
27
|
+
assert.ok(connResult.toolsCount >= 10);
|
|
28
|
+
|
|
29
|
+
const schemas = manager.getToolsSchema();
|
|
30
|
+
assert.ok(schemas.length >= 10);
|
|
31
|
+
const searchTool = schemas.find(s => s.function.name === 'mcp_local_kiku_search_works');
|
|
32
|
+
assert.ok(searchTool, 'Tool should be registered with server namespace prefix');
|
|
33
|
+
assert.equal(searchTool.type, 'function');
|
|
34
|
+
assert.ok(searchTool.function.description.includes('[MCP Server: local_kiku]'));
|
|
35
|
+
|
|
36
|
+
const callResult = await manager.callTool('mcp_local_kiku_search_works', { year: 2026 });
|
|
37
|
+
assert.equal(callResult.status, 'success');
|
|
38
|
+
assert.equal(callResult.server, 'local_kiku');
|
|
39
|
+
assert.equal(callResult.tool, 'search_works');
|
|
40
|
+
assert.ok(callResult.count > 0);
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
const directCall = await manager.callTool('search_works', { year: 2026 });
|
|
44
|
+
assert.equal(directCall.status, 'success');
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const status = manager.getStatus();
|
|
48
|
+
assert.equal(status.activeServersCount, 1);
|
|
49
|
+
assert.equal(status.servers[0].name, 'local_kiku');
|
|
50
|
+
} finally {
|
|
51
|
+
await manager.disconnectAll();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('KikuAgent dynamically registers MCP tools into tool schema and system prompt', { timeout: 30_000 }, async () => {
|
|
56
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'kiku-mcp-agent-test-'));
|
|
57
|
+
const agent = new KikuAgent({
|
|
58
|
+
mcpManager: new McpClientManager({
|
|
59
|
+
autoLoad: false,
|
|
60
|
+
globalConfigPath: path.join(tempDir, 'mcp_servers.json'),
|
|
61
|
+
}),
|
|
62
|
+
client: {
|
|
63
|
+
provider: 'lmstudio',
|
|
64
|
+
model: 'test-model',
|
|
65
|
+
async chat() { return { role: 'assistant', content: 'test response' }; },
|
|
66
|
+
async checkConnection() { return { connected: true, models: ['test-model'] }; }
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
await agent.ready();
|
|
70
|
+
|
|
71
|
+
const initialToolsCount = agent.getAllToolsSchema().length;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const conn = await agent.connectMcp('test_kiku', {
|
|
75
|
+
command: process.execPath,
|
|
76
|
+
args: [stdioServerPath],
|
|
77
|
+
cwd: process.cwd(),
|
|
78
|
+
});
|
|
79
|
+
assert.equal(conn.success, true);
|
|
80
|
+
|
|
81
|
+
const updatedTools = agent.getAllToolsSchema();
|
|
82
|
+
assert.ok(updatedTools.length > initialToolsCount);
|
|
83
|
+
assert.ok(updatedTools.some(t => t.function.name.startsWith('mcp_test_kiku_')));
|
|
84
|
+
assert.ok(agent.systemPrompt.includes('mcp_test_kiku_'));
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
assert.equal(await routeCommand('/mcp status', { agent, sub: 'status' }), 'HANDLED');
|
|
88
|
+
assert.equal(await routeCommand('/mcp tools', { agent, sub: 'tools' }), 'HANDLED');
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
const disconn = await agent.disconnectMcp('test_kiku');
|
|
92
|
+
assert.equal(disconn, true);
|
|
93
|
+
assert.equal(agent.getAllToolsSchema().length, initialToolsCount);
|
|
94
|
+
} finally {
|
|
95
|
+
await agent.mcpManager.disconnectAll();
|
|
96
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('DynamicToolRouter pins tools mentioned with @ in the prompt with top priority', () => {
|
|
101
|
+
const agent = new KikuAgent({
|
|
102
|
+
mcpManager: new McpClientManager({ autoLoad: false }),
|
|
103
|
+
client: {
|
|
104
|
+
provider: 'lmstudio',
|
|
105
|
+
model: 'test-model',
|
|
106
|
+
async chat() { return { role: 'assistant', content: 'test response' }; },
|
|
107
|
+
async checkConnection() { return { connected: true, models: ['test-model'] }; }
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const routed = agent.router.routeToolsForQuery('Please tell me about her works @search_works');
|
|
112
|
+
assert.ok(routed.length > 0);
|
|
113
|
+
assert.equal(routed[0].function.name, 'search_works');
|
|
114
|
+
|
|
115
|
+
const routedAwards = agent.router.routeToolsForQuery('@get_awards check all music awards');
|
|
116
|
+
assert.ok(routedAwards.length > 0);
|
|
117
|
+
assert.equal(routedAwards[0].function.name, 'get_awards');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('McpClientManager persists configurations across disconnects and supports save/remove', async () => {
|
|
121
|
+
const manager = new McpClientManager({ autoLoad: false });
|
|
122
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'kiku-mcp-config-test-'));
|
|
123
|
+
const configPath = path.join(tempDir, 'mcp_servers.json');
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
manager.persistedConfigs.set('test_server', {
|
|
127
|
+
type: 'streamable-http',
|
|
128
|
+
url: 'https://example.com/mcp',
|
|
129
|
+
enabled: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const status = manager.getStatus();
|
|
133
|
+
assert.equal(status.totalConfiguredCount, 1);
|
|
134
|
+
assert.equal(status.servers[0].name, 'test_server');
|
|
135
|
+
assert.equal(status.servers[0].status, 'saved_offline');
|
|
136
|
+
|
|
137
|
+
const saveRes = await manager.savePersistedConfig(configPath);
|
|
138
|
+
assert.equal(saveRes.success, true);
|
|
139
|
+
assert.equal(saveRes.filePath, configPath);
|
|
140
|
+
assert.ok(saveRes.count >= 1);
|
|
141
|
+
const saved = JSON.parse(await readFile(configPath, 'utf8'));
|
|
142
|
+
assert.equal(saved.mcpServers.test_server.url, 'https://example.com/mcp');
|
|
143
|
+
|
|
144
|
+
const removed = await manager.remove('test_server', { persist: false });
|
|
145
|
+
assert.equal(removed, true);
|
|
146
|
+
assert.equal(manager.persistedConfigs.has('test_server'), false);
|
|
147
|
+
} finally {
|
|
148
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('McpClientManager permanently removes a server from every loaded config source', async () => {
|
|
153
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'kiku-mcp-source-remove-test-'));
|
|
154
|
+
const globalConfigPath = path.join(tempDir, 'mcp_servers.json');
|
|
155
|
+
const workspaceConfigPath = path.join(tempDir, 'opencode.json');
|
|
156
|
+
|
|
157
|
+
await writeFile(globalConfigPath, JSON.stringify({
|
|
158
|
+
mcpServers: {
|
|
159
|
+
duplicate_server: { type: 'remote', url: 'https://global.example/mcp', enabled: false },
|
|
160
|
+
global_only: { type: 'remote', url: 'https://global-only.example/mcp', enabled: false },
|
|
161
|
+
},
|
|
162
|
+
}, null, 2));
|
|
163
|
+
await writeFile(workspaceConfigPath, JSON.stringify({
|
|
164
|
+
$schema: 'https://opencode.ai/config.json',
|
|
165
|
+
mcp: {
|
|
166
|
+
duplicate_server: { type: 'remote', url: 'https://workspace.example/mcp', enabled: false },
|
|
167
|
+
workspace_only: { type: 'remote', url: 'https://workspace-only.example/mcp', enabled: false },
|
|
168
|
+
},
|
|
169
|
+
}, null, 2));
|
|
170
|
+
|
|
171
|
+
const managerOptions = {
|
|
172
|
+
globalConfigPath,
|
|
173
|
+
configPaths: [globalConfigPath, workspaceConfigPath],
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const manager = new McpClientManager(managerOptions);
|
|
178
|
+
await manager.loadPersistedServers();
|
|
179
|
+
|
|
180
|
+
const before = manager.getStatus().servers.find(server => server.name === 'duplicate_server');
|
|
181
|
+
assert.ok(before);
|
|
182
|
+
assert.deepEqual(new Set(before.sources), new Set([globalConfigPath, workspaceConfigPath]));
|
|
183
|
+
|
|
184
|
+
assert.equal(await manager.remove('duplicate_server'), true);
|
|
185
|
+
|
|
186
|
+
const savedGlobal = JSON.parse(await readFile(globalConfigPath, 'utf8'));
|
|
187
|
+
const savedWorkspace = JSON.parse(await readFile(workspaceConfigPath, 'utf8'));
|
|
188
|
+
assert.equal(savedGlobal.mcpServers.duplicate_server, undefined);
|
|
189
|
+
assert.equal(savedWorkspace.mcp.duplicate_server, undefined);
|
|
190
|
+
assert.ok(savedGlobal.mcpServers.global_only);
|
|
191
|
+
assert.ok(savedWorkspace.mcp.workspace_only);
|
|
192
|
+
assert.equal(savedWorkspace.$schema, 'https://opencode.ai/config.json');
|
|
193
|
+
|
|
194
|
+
const restartedManager = new McpClientManager(managerOptions);
|
|
195
|
+
await restartedManager.loadPersistedServers();
|
|
196
|
+
assert.equal(restartedManager.persistedConfigs.has('duplicate_server'), false);
|
|
197
|
+
} finally {
|
|
198
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { ModelManager } from '../modelManager.js';
|
|
5
|
+
|
|
6
|
+
function clientWith(models, connected = true) {
|
|
7
|
+
return {
|
|
8
|
+
model: 'requested/model',
|
|
9
|
+
async checkConnection() {
|
|
10
|
+
return connected ? { connected: true, models } : { connected: false, models: [], error: 'offline' };
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test('model manager keeps an explicitly requested loaded model', async () => {
|
|
16
|
+
const client = clientWith(['requested/model', 'google/gemma-4-e4b']);
|
|
17
|
+
const result = await new ModelManager(client).resolve();
|
|
18
|
+
assert.equal(result.state, 'ready');
|
|
19
|
+
assert.equal(result.usedFallback, false);
|
|
20
|
+
assert.equal(client.model, 'requested/model');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('model manager selects a chat fallback when the requested model is missing', async () => {
|
|
24
|
+
const client = clientWith(['text-embedding-model', 'google/gemma-4-e4b']);
|
|
25
|
+
const result = await new ModelManager(client).resolve();
|
|
26
|
+
assert.equal(result.state, 'fallback_selected');
|
|
27
|
+
assert.equal(result.selectedModel, 'google/gemma-4-e4b');
|
|
28
|
+
assert.equal(client.model, 'google/gemma-4-e4b');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('model manager prefers the provider recommendation over an arbitrary catalog model', async () => {
|
|
32
|
+
const client = clientWith(['random/chat-model', 'gpt-5.6-luna']);
|
|
33
|
+
client.provider = 'openai';
|
|
34
|
+
client.getProviderInfo = () => ({ recommendedModels: [{ id: 'gpt-5.6-luna' }] });
|
|
35
|
+
const result = await new ModelManager(client).resolve();
|
|
36
|
+
assert.equal(result.state, 'fallback_selected');
|
|
37
|
+
assert.equal(result.selectedModel, 'gpt-5.6-luna');
|
|
38
|
+
assert.equal(client.model, 'gpt-5.6-luna');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('model manager reports online server with no loaded model', async () => {
|
|
42
|
+
const result = await new ModelManager(clientWith([])).resolve();
|
|
43
|
+
assert.equal(result.state, 'server_online_no_models');
|
|
44
|
+
assert.equal(result.selectedModel, undefined);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('model manager rejects unavailable and non-chat manual selections', () => {
|
|
48
|
+
const manager = new ModelManager(clientWith([]));
|
|
49
|
+
assert.throws(() => manager.select('missing', ['chat']), /not available/);
|
|
50
|
+
assert.throws(() => manager.select('embedding-model', ['embedding-model']), /does not appear to support chat/);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('model manager normalizes legacy model aliases before availability checks', () => {
|
|
54
|
+
const client = clientWith([]);
|
|
55
|
+
client.provider = 'deepseek';
|
|
56
|
+
const manager = new ModelManager(client);
|
|
57
|
+
const selected = manager.select('deepseek-chat', ['deepseek-v4-flash']);
|
|
58
|
+
assert.equal(selected, 'deepseek-v4-flash');
|
|
59
|
+
assert.equal(client.model, 'deepseek-v4-flash');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('model manager matches Q8_0 quantization with GGUF file paths and publishers in LM Studio', async () => {
|
|
63
|
+
const client = {
|
|
64
|
+
provider: 'lmstudio',
|
|
65
|
+
model: 'google/gemma-4-e4b-Q8_0',
|
|
66
|
+
async checkConnection() {
|
|
67
|
+
return {
|
|
68
|
+
connected: true,
|
|
69
|
+
models: [
|
|
70
|
+
'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q4_K_M.gguf',
|
|
71
|
+
'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q8_0.gguf'
|
|
72
|
+
],
|
|
73
|
+
activeLoadedModel: 'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q8_0.gguf'
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const manager = new ModelManager(client, { requestedModel: 'google/gemma-4-e4b-Q8_0' });
|
|
79
|
+
const result = await manager.resolve();
|
|
80
|
+
assert.equal(result.state, 'ready');
|
|
81
|
+
assert.equal(result.selectedModel, 'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q8_0.gguf');
|
|
82
|
+
assert.equal(client.model, 'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q8_0.gguf');
|
|
83
|
+
|
|
84
|
+
const inspection = await manager.inspect();
|
|
85
|
+
assert.equal(inspection.state, 'ready');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('model manager allows selecting shorthand quantizations like Q8_0, q8, Q6_K in LM Studio', () => {
|
|
89
|
+
const client = {
|
|
90
|
+
provider: 'lmstudio',
|
|
91
|
+
model: 'google/gemma-4-e4b',
|
|
92
|
+
};
|
|
93
|
+
const available = [
|
|
94
|
+
'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q4_K_M.gguf',
|
|
95
|
+
'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q8_0.gguf'
|
|
96
|
+
];
|
|
97
|
+
const manager = new ModelManager(client);
|
|
98
|
+
|
|
99
|
+
const selectedQ8 = manager.select('Q8_0', available);
|
|
100
|
+
assert.equal(selectedQ8, 'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q8_0.gguf');
|
|
101
|
+
assert.equal(client.model, 'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q8_0.gguf');
|
|
102
|
+
|
|
103
|
+
const selectedQ8Short = manager.select('q8', available);
|
|
104
|
+
assert.equal(selectedQ8Short, 'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q8_0.gguf');
|
|
105
|
+
|
|
106
|
+
const selectedQ4 = manager.select('q4_k_m', available);
|
|
107
|
+
assert.equal(selectedQ4, 'bartowski/gemma-4-e4b-GGUF/gemma-4-e4b-Q4_K_M.gguf');
|
|
108
|
+
});
|
|
109
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { detectDominantLanguage } from '../agentCore.js';
|
|
4
|
+
import { DynamicToolRouter } from '../toolRouter.js';
|
|
5
|
+
import { executeTool } from '../tools.js';
|
|
6
|
+
|
|
7
|
+
export const MULTILINGUAL_QUERIES = Object.freeze([
|
|
8
|
+
{
|
|
9
|
+
lang: 'th',
|
|
10
|
+
text: 'จวีจิ้งอี เกิดวันที่เท่าไหร่ และมีผลงานเพลงอะไรบ้างค่ะ',
|
|
11
|
+
expectedLanguage: 'th'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
lang: 'zh',
|
|
15
|
+
text: '请问鞠婧祎在2026年有哪些新剧待播?',
|
|
16
|
+
expectedLanguage: 'zh'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
lang: 'en',
|
|
20
|
+
text: 'What are the main awards won by Ju Jingyi in her solo career?',
|
|
21
|
+
expectedLanguage: 'en'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
lang: 'th',
|
|
25
|
+
text: 'Ju Jingyi (鞠婧祎) ผลงานละครเรื่อง ฮวาเจียนลิ่ง (In Blossom) ฉายปีไหนคะ',
|
|
26
|
+
expectedLanguage: 'th'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
lang: 'zh',
|
|
30
|
+
text: '鞠婧祎 Ju Jingyi 个人单曲《每一天》的发行年份和MV',
|
|
31
|
+
expectedLanguage: 'zh'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
lang: 'en',
|
|
35
|
+
text: 'Ju Jingyi Chinese name 鞠婧祎 and fandom name Miju 蜜橘 details',
|
|
36
|
+
expectedLanguage: 'en'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
lang: 'ja',
|
|
40
|
+
text: '鞠婧祎(ジュ・ジンイー)の最新ドラマと代表曲を教えてください',
|
|
41
|
+
expectedLanguage: 'ja'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
lang: 'ko',
|
|
45
|
+
text: '쥐징이의 최신 드라마와 대표곡을 알려주세요',
|
|
46
|
+
expectedLanguage: 'ko'
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
test('Multilingual Suite: Accurately detects primary language in mixed bilingual queries', () => {
|
|
51
|
+
for (const item of MULTILINGUAL_QUERIES) {
|
|
52
|
+
const detected = detectDominantLanguage(item.text);
|
|
53
|
+
assert.equal(detected, item.expectedLanguage);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('Multilingual Suite: Universal and domain search tools find records across Chinese, Thai, and English titles', async () => {
|
|
58
|
+
const cnRes = await executeTool('search_works', { query: '花间令' });
|
|
59
|
+
assert.equal(cnRes.status, 'success');
|
|
60
|
+
assert.ok(cnRes.count > 0);
|
|
61
|
+
|
|
62
|
+
const thRes = await executeTool('search_works', { query: 'ฮวาเจียนลิ่ง' });
|
|
63
|
+
assert.equal(thRes.status, 'success');
|
|
64
|
+
assert.ok(thRes.count > 0);
|
|
65
|
+
|
|
66
|
+
const enRes = await executeTool('search_works', { query: 'In Blossom' });
|
|
67
|
+
assert.equal(enRes.status, 'success');
|
|
68
|
+
assert.ok(enRes.count > 0);
|
|
69
|
+
});
|