kiku-agent 1.0.0 → 1.0.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 +293 -122
- package/package.json +60 -61
- package/server/agent/agentCore.js +892 -782
- package/server/agent/appPaths.js +21 -0
- package/server/agent/audioPlayer.js +739 -673
- package/server/agent/cli.js +1414 -1111
- package/server/agent/commandRegistry.js +74 -58
- package/server/agent/commands.js +1694 -788
- package/server/agent/config.js +264 -90
- package/server/agent/contextCompactor.js +79 -0
- package/server/agent/doctor.js +69 -0
- package/server/agent/eventBus.js +107 -103
- package/server/agent/gpuMetrics.js +189 -0
- package/server/agent/llmClient.js +369 -200
- package/server/agent/mcpClient.js +364 -0
- package/server/agent/memory.js +670 -566
- package/server/agent/modelManager.js +77 -0
- package/server/agent/research.js +202 -0
- package/server/agent/session.js +244 -239
- package/server/agent/subagent.js +116 -0
- package/server/agent/theme.js +125 -124
- package/server/agent/toolRouter.js +201 -0
- package/server/agent/tools.js +1679 -1528
- package/server/agent/ui.js +760 -469
- package/server/agent/youtubeAuth.js +144 -65
- package/server/mcp/claude-desktop-config.example.json +1 -1
- package/server/mcp/data/discography.js +36 -36
- package/server/mcp/dataAdapter.js +102 -0
- package/server/mcp/examples/claude-code.mcp.example.json +19 -0
- package/server/mcp/examples/claude-desktop.example.json +11 -0
- package/server/mcp/examples/cline.example.json +13 -0
- package/server/mcp/examples/cursor.example.json +14 -0
- package/server/mcp/examples/gemini-cli.example.json +17 -0
- package/server/mcp/examples/opencode.example.json +21 -0
- package/server/mcp/examples/vscode.example.json +19 -0
- package/server/mcp/examples/windsurf.example.json +14 -0
- package/server/mcp/handler.js +85 -159
- package/server/mcp/security.js +140 -138
- package/server/mcp/server.js +129 -100
- package/server/mcp/stdio.js +4 -2
package/server/agent/config.js
CHANGED
|
@@ -1,90 +1,264 @@
|
|
|
1
|
-
export const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
1
|
+
export const CLOUD_PROVIDERS = {
|
|
2
|
+
lmstudio: {
|
|
3
|
+
id: 'lmstudio',
|
|
4
|
+
name: 'LM Studio (Local)',
|
|
5
|
+
baseUrl: process.env.LMSTUDIO_BASE_URL || process.env.OPENAI_BASE_URL || 'http://127.0.0.1:1234/v1',
|
|
6
|
+
apiKey: process.env.LMSTUDIO_API_KEY || 'lm-studio',
|
|
7
|
+
defaultModel: 'google/gemma-4-e4b',
|
|
8
|
+
isLocal: true,
|
|
9
|
+
envKey: 'LMSTUDIO_BASE_URL',
|
|
10
|
+
docUrl: 'https://lmstudio.ai/docs',
|
|
11
|
+
apiRefUrl: 'https://lmstudio.ai/docs/api/local-server',
|
|
12
|
+
pricingSummary: '100% Free / Local (No API Key required)',
|
|
13
|
+
features: ['local', 'private', 'offline', 'zero-cost', 'openai-compatible'],
|
|
14
|
+
recommendedModels: [
|
|
15
|
+
{ id: 'google/gemma-4-e4b', name: 'Gemma 4 e4b (Default / Q4_K_M)', tier: 'free', cost: '$0.00 (Local)', context: '8k', hasTools: true, description: 'Recommended default. Balanced 4-bit quantization (Q4_K_M) — fastest inference and lowest RAM usage.' },
|
|
16
|
+
{ id: 'google/gemma-4-e4b-Q4_K_M', name: 'Gemma 4 e4b (Q4_K_M Quant)', tier: 'free', cost: '$0.00 (Local)', context: '8k', hasTools: true, description: '4-bit medium quantization (Q4_K_M) optimized for speed and low memory footprint (~2.5GB RAM).' },
|
|
17
|
+
{ id: 'google/gemma-4-e4b-Q6_K', name: 'Gemma 4 e4b (Q6_K Quant)', tier: 'free', cost: '$0.00 (Local)', context: '8k', hasTools: true, description: '6-bit quantization (Q6_K) offering higher precision and near-original fidelity (~3.5GB RAM).' },
|
|
18
|
+
{ id: 'google/gemma-4-e4b-Q8_0', name: 'Gemma 4 e4b (Q8_0 Quant)', tier: 'free', cost: '$0.00 (Local)', context: '8k', hasTools: true, description: '8-bit full quantization (Q8_0) for maximum accuracy and reasoning precision (~4.5GB RAM).' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
openai: {
|
|
22
|
+
id: 'openai',
|
|
23
|
+
name: 'OpenAI Cloud',
|
|
24
|
+
baseUrl: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
|
|
25
|
+
apiKey: process.env.OPENAI_API_KEY || '',
|
|
26
|
+
defaultModel: process.env.OPENAI_MODEL || 'gpt-4o-mini',
|
|
27
|
+
isLocal: false,
|
|
28
|
+
envKey: 'OPENAI_API_KEY',
|
|
29
|
+
docUrl: 'https://platform.openai.com/docs',
|
|
30
|
+
apiRefUrl: 'https://platform.openai.com/docs/api-reference',
|
|
31
|
+
pricingSummary: 'Pay-per-token (gpt-4o-mini: ~$0.15/1M in, $0.60/1M out)',
|
|
32
|
+
features: ['tools', 'streaming', 'vision', 'reasoning', 'system-prompt'],
|
|
33
|
+
recommendedModels: [
|
|
34
|
+
{ id: 'gpt-4o-mini', name: 'GPT-4o Mini', tier: 'ultra-cheap', cost: '$0.15 / 1M in', context: '128k', hasTools: true, description: 'Recommended default. Fast, intelligent, and highly cost-effective with tool use.' },
|
|
35
|
+
{ id: 'gpt-4o', name: 'GPT-4o', tier: 'flagship', cost: '$2.50 / 1M in', context: '128k', hasTools: true, description: 'OpenAI flagship multimodal model for complex multimodal & reasoning tasks.' },
|
|
36
|
+
{ id: 'o3-mini', name: 'o3 Mini', tier: 'reasoning', cost: '$1.10 / 1M in', context: '200k', hasTools: true, description: 'Fast, cost-efficient reasoning model with function calling capabilities.' },
|
|
37
|
+
{ id: 'o1-mini', name: 'o1 Mini', tier: 'reasoning', cost: '$1.10 / 1M in', context: '128k', hasTools: true, description: 'Reasoning model specialized for complex STEM logic and coding.' },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
openrouter: {
|
|
41
|
+
id: 'openrouter',
|
|
42
|
+
name: 'OpenRouter Cloud',
|
|
43
|
+
baseUrl: process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1',
|
|
44
|
+
apiKey: process.env.OPENROUTER_API_KEY || '',
|
|
45
|
+
defaultModel: process.env.OPENROUTER_MODEL || 'google/gemini-2.5-flash',
|
|
46
|
+
extraHeaders: {
|
|
47
|
+
'HTTP-Referer': 'https://jujingyi-kikuuu.vercel.app/',
|
|
48
|
+
'X-Title': 'Kiku Agent',
|
|
49
|
+
},
|
|
50
|
+
isLocal: false,
|
|
51
|
+
envKey: 'OPENROUTER_API_KEY',
|
|
52
|
+
docUrl: 'https://openrouter.ai/docs/quick-start',
|
|
53
|
+
apiRefUrl: 'https://openrouter.ai/docs/api_reference/overview',
|
|
54
|
+
pricingSummary: 'Universal aggregator with free tiers & flexible pay-as-you-go',
|
|
55
|
+
features: ['universal-routing', 'tools', 'streaming', 'free-tier', 'fallback'],
|
|
56
|
+
recommendedModels: [
|
|
57
|
+
{ id: 'google/gemini-2.5-flash', name: 'Gemini 2.5 Flash', tier: 'ultra-cheap', cost: '$0.10 / 1M in', context: '1M', hasTools: true, description: 'Super fast with huge 1M context window and native function calling.' },
|
|
58
|
+
{ id: 'deepseek/deepseek-chat', name: 'DeepSeek Chat (V3)', tier: 'ultra-cheap', cost: '$0.14 / 1M in', context: '64k', hasTools: true, description: 'DeepSeek-V3 671B MoE via OpenRouter at ultra-low price.' },
|
|
59
|
+
{ id: 'deepseek/deepseek-r1', name: 'DeepSeek Reasoner (R1)', tier: 'reasoning', cost: '$0.55 / 1M in', context: '64k', hasTools: false, description: 'Full DeepSeek-R1 reasoning with Chain-of-Thought output.' },
|
|
60
|
+
{ id: 'meta-llama/llama-3.3-70b-instruct', name: 'Llama 3.3 70B Instruct', tier: 'balanced', cost: '$0.40 / 1M in', context: '128k', hasTools: true, description: 'State-of-the-art open-weights flagship model.' },
|
|
61
|
+
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', tier: 'ultra-cheap', cost: '$0.15 / 1M in', context: '128k', hasTools: true, description: 'OpenAI mini model routed via OpenRouter.' },
|
|
62
|
+
{ id: 'qwen/qwen-2.5-72b-instruct', name: 'Qwen 2.5 72B Instruct', tier: 'balanced', cost: '$0.35 / 1M in', context: '128k', hasTools: true, description: 'Top-tier open-weights multilingual and coding model.' },
|
|
63
|
+
{ id: 'meta-llama/llama-3.1-8b-instruct:free', name: 'Llama 3.1 8B (Free)', tier: 'free', cost: '$0.00', context: '128k', hasTools: true, description: 'Free tier Llama 3.1 8B on OpenRouter.' },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
groq: {
|
|
67
|
+
id: 'groq',
|
|
68
|
+
name: 'Groq Cloud',
|
|
69
|
+
baseUrl: process.env.GROQ_BASE_URL || 'https://api.groq.com/openai/v1',
|
|
70
|
+
apiKey: process.env.GROQ_API_KEY || '',
|
|
71
|
+
defaultModel: process.env.GROQ_MODEL || 'llama-3.3-70b-versatile',
|
|
72
|
+
isLocal: false,
|
|
73
|
+
envKey: 'GROQ_API_KEY',
|
|
74
|
+
docUrl: 'https://console.groq.com/docs/quickstart',
|
|
75
|
+
apiRefUrl: 'https://console.groq.com/docs/api-reference',
|
|
76
|
+
pricingSummary: 'Ultra-fast LPU inference (llama-3.1-8b: $0.05/1M, llama-3.3-70b: $0.59/1M)',
|
|
77
|
+
features: ['lpu-speed', 'tools', 'streaming', 'openai-compatible'],
|
|
78
|
+
recommendedModels: [
|
|
79
|
+
{ id: 'llama-3.3-70b-versatile', name: 'Llama 3.3 70B Versatile', tier: 'balanced', cost: '$0.59 / 1M in', context: '128k', hasTools: true, description: 'Recommended default. 70B flagship quality with fast LPU speed and tool use.' },
|
|
80
|
+
{ id: 'llama-3.1-8b-instant', name: 'Llama 3.1 8B Instant', tier: 'ultra-cheap', cost: '$0.05 / 1M in', context: '128k', hasTools: true, description: 'Blazing fast (~800 tokens/s) and ultra-low cost for quick inquiries.' },
|
|
81
|
+
{ id: 'gpt-oss-120b', name: 'GPT-OSS 120B', tier: 'balanced', cost: '$0.59 / 1M in', context: '128k', hasTools: true, description: 'Open-weights 120B flagship model running with ultra-fast Groq LPU inference.' },
|
|
82
|
+
{ id: 'mixtral-8x7b-32768', name: 'Mixtral 8x7B', tier: 'cheap', cost: '$0.24 / 1M in', context: '32k', hasTools: true, description: 'Mistral MoE architecture with 32k context.' },
|
|
83
|
+
{ id: 'gemma2-9b-it', name: 'Gemma 2 9B IT', tier: 'cheap', cost: '$0.20 / 1M in', context: '8k', hasTools: true, description: 'Google Gemma 2 on Groq LPU hardware.' },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
deepseek: {
|
|
87
|
+
id: 'deepseek',
|
|
88
|
+
name: 'DeepSeek Cloud',
|
|
89
|
+
baseUrl: process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com/v1',
|
|
90
|
+
apiKey: process.env.DEEPSEEK_API_KEY || '',
|
|
91
|
+
defaultModel: process.env.DEEPSEEK_MODEL || 'deepseek-chat',
|
|
92
|
+
isLocal: false,
|
|
93
|
+
envKey: 'DEEPSEEK_API_KEY',
|
|
94
|
+
docUrl: 'https://api-docs.deepseek.com',
|
|
95
|
+
apiRefUrl: 'https://api-docs.deepseek.com/api/create-chat-completion',
|
|
96
|
+
pricingSummary: 'Ultra-low cost (deepseek-chat: $0.14 cached / $0.27 uncached / 1M)',
|
|
97
|
+
features: ['deepseek-v3', 'deepseek-r1-reasoning', 'tools', 'streaming', 'context-cache'],
|
|
98
|
+
recommendedModels: [
|
|
99
|
+
{ id: 'deepseek-chat', name: 'DeepSeek Chat (V3)', tier: 'ultra-cheap', cost: '$0.14-$0.27 / 1M in', context: '64k', hasTools: true, description: 'Recommended default. DeepSeek-V3 671B MoE, top-tier reasoning, tool calling, fast & ultra-cheap.' },
|
|
100
|
+
{ id: 'deepseek-reasoner', name: 'DeepSeek Reasoner (R1)', tier: 'reasoning', cost: '$0.55 / 1M in', context: '64k', hasTools: false, description: 'DeepSeek-R1 full Chain-of-Thought reasoning with native thinking output.' },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
gemini: {
|
|
104
|
+
id: 'gemini',
|
|
105
|
+
name: 'Google Gemini Cloud',
|
|
106
|
+
baseUrl: process.env.GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta/openai/',
|
|
107
|
+
apiKey: process.env.GEMINI_API_KEY || '',
|
|
108
|
+
defaultModel: process.env.GEMINI_MODEL || 'gemini-2.5-flash',
|
|
109
|
+
isLocal: false,
|
|
110
|
+
envKey: 'GEMINI_API_KEY',
|
|
111
|
+
docUrl: 'https://ai.google.dev/docs',
|
|
112
|
+
apiRefUrl: 'https://ai.google.dev/api',
|
|
113
|
+
pricingSummary: 'Ultra-cheap (gemini-2.5-flash: $0.10/1M in, $0.40/1M out)',
|
|
114
|
+
features: ['1m-context', 'multimodal', 'tools', 'streaming', 'thinking'],
|
|
115
|
+
recommendedModels: [
|
|
116
|
+
{ id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash', tier: 'ultra-cheap', cost: '$0.10 / 1M in', context: '1M', hasTools: true, description: 'Recommended default. Advanced reasoning and thinking capabilities with 1M context.' },
|
|
117
|
+
{ id: 'gemini-2.5-flash-lite', name: 'Gemini 2.5 Flash Lite', tier: 'lowest-cost', cost: '$0.075 / 1M in', context: '1M', hasTools: true, description: 'Cost-optimized 2.5 lightweight model for high volume queries.' },
|
|
118
|
+
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', tier: 'flagship', cost: '$1.25 / 1M in', context: '2M', hasTools: true, description: 'Deep reasoning and 2M context for complex multimodal analysis.' },
|
|
119
|
+
{ id: 'gemini-3.1-pro', name: 'Gemini 3.1 Pro', tier: 'flagship', cost: '$1.50 / 1M in', context: '2M', hasTools: true, description: 'Next-gen Gemini 3.1 flagship with state-of-the-art coding and multimodal reasoning.' },
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
custom: {
|
|
123
|
+
id: 'custom',
|
|
124
|
+
name: 'Custom OpenAI-Compatible',
|
|
125
|
+
baseUrl: process.env.LLM_BASE_URL || '',
|
|
126
|
+
apiKey: process.env.LLM_API_KEY || '',
|
|
127
|
+
defaultModel: process.env.LLM_MODEL || 'default',
|
|
128
|
+
isLocal: false,
|
|
129
|
+
envKey: 'LLM_BASE_URL',
|
|
130
|
+
docUrl: 'https://platform.openai.com/docs/api-reference',
|
|
131
|
+
apiRefUrl: 'https://platform.openai.com/docs/api-reference/chat',
|
|
132
|
+
pricingSummary: 'Self-hosted or Custom Gateway',
|
|
133
|
+
features: ['custom-endpoint', 'openai-compatible', 'tools', 'streaming'],
|
|
134
|
+
recommendedModels: [
|
|
135
|
+
{ id: 'default', name: 'Default Model', tier: 'custom', cost: 'Custom', context: 'Custom', hasTools: true, description: 'Custom model specified via LLM_MODEL or /model use <id>.' },
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export function resolveDefaultProvider() {
|
|
141
|
+
const explicit = process.env.LLM_PROVIDER?.toLowerCase();
|
|
142
|
+
if (explicit && CLOUD_PROVIDERS[explicit]) return explicit;
|
|
143
|
+
|
|
144
|
+
if (process.env.OPENROUTER_API_KEY) return 'openrouter';
|
|
145
|
+
if (process.env.OPENAI_API_KEY) return 'openai';
|
|
146
|
+
if (process.env.GEMINI_API_KEY) return 'gemini';
|
|
147
|
+
if (process.env.DEEPSEEK_API_KEY) return 'deepseek';
|
|
148
|
+
if (process.env.GROQ_API_KEY) return 'groq';
|
|
149
|
+
if (process.env.LLM_API_KEY && process.env.LLM_BASE_URL) return 'custom';
|
|
150
|
+
|
|
151
|
+
return 'lmstudio';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function getProvider(id) {
|
|
155
|
+
const pId = String(id || '').toLowerCase().trim();
|
|
156
|
+
return CLOUD_PROVIDERS[pId] || null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getProviderList() {
|
|
160
|
+
return Object.values(CLOUD_PROVIDERS);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function getRecommendedModels(providerId) {
|
|
164
|
+
const provider = getProvider(providerId);
|
|
165
|
+
return provider ? (provider.recommendedModels || []) : [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const defaultProviderId = resolveDefaultProvider();
|
|
169
|
+
const defaultProviderConfig = CLOUD_PROVIDERS[defaultProviderId] || CLOUD_PROVIDERS.lmstudio;
|
|
170
|
+
|
|
171
|
+
export const config = {
|
|
172
|
+
activeProvider: defaultProviderId,
|
|
173
|
+
providers: CLOUD_PROVIDERS,
|
|
174
|
+
|
|
175
|
+
lmStudio: {
|
|
176
|
+
baseUrl: defaultProviderConfig.baseUrl,
|
|
177
|
+
apiKey: defaultProviderConfig.apiKey,
|
|
178
|
+
model: defaultProviderConfig.defaultModel,
|
|
179
|
+
defaultModel: defaultProviderConfig.defaultModel,
|
|
180
|
+
temperature: 0.3,
|
|
181
|
+
maxTokens: 1536,
|
|
182
|
+
requestTimeoutMs: 180000,
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
agent: {
|
|
186
|
+
maxIterations: 4,
|
|
187
|
+
slidingWindowSize: 8,
|
|
188
|
+
language: 'auto',
|
|
189
|
+
systemPersona: `You are "Kiku AI" (จวีจิ้งอี AI ผู้ช่วยอัจฉริยะ / 鞠婧祎智能AI助手), an expert AI specialist on the Chinese singer, actress, and artist Ju Jingyi (鞠婧祎 - จวีจิ้งอี).
|
|
190
|
+
You have access to a verified API database of her dramas, songs, character roles, brand endorsements, timeline milestones, awards, and music videos, as well as LIVE Internet Search (web_search, fetch_webpage, web_extract, fetch_json).
|
|
191
|
+
|
|
192
|
+
=== CRITICAL INSTRUCTIONS ===
|
|
193
|
+
|
|
194
|
+
1. PROPORTIONAL RESPONSE & GREETINGS:
|
|
195
|
+
- CASUAL GREETINGS ("Hi", "Hello", "สวัสดี", "你好"): Reply warmly in 1-2 sentences. DO NOT call tools or dump catalogs.
|
|
196
|
+
- SHORT QUESTIONS: Give direct, concise 1-3 sentence answer.
|
|
197
|
+
- BROAD REQUESTS: Provide structured, well-organized list with key details and clickable links.
|
|
198
|
+
|
|
199
|
+
2. DYNAMIC LANGUAGE MIRRORING:
|
|
200
|
+
- THAI INPUT ➔ Reply in polite Thai (ใช้ ค่ะ/นะคะ เสมอ)
|
|
201
|
+
- CHINESE INPUT ➔ Reply in fluent, polite Chinese (简体/繁体中文)
|
|
202
|
+
- ENGLISH INPUT ➔ Reply in polished, professional English
|
|
203
|
+
- MIXED INPUT ➔ Match dominant language while preserving bilingual titles
|
|
204
|
+
|
|
205
|
+
3. RESPONSE FORMATTING (CRITICAL — NO SYSTEM LEAK):
|
|
206
|
+
- NEVER include "Thinking Process", "Tool Calling", "Action:", "Action Input:", or any internal reasoning steps in your response text.
|
|
207
|
+
- NEVER include tool call JSON, function names, or parameter schemas in your response.
|
|
208
|
+
- NEVER include advertisements, sponsor mentions, or promotional content in your response.
|
|
209
|
+
- Emoji are optional and must be semantic: use at most one relevant emoji per response, never decorate every heading or bullet.
|
|
210
|
+
- Your response must be CLEAN user-facing content ONLY — formatted with Markdown headers (###), bold (**text**), bullet points (•), and clickable links.
|
|
211
|
+
- Think silently using tools, then present ONLY the final answer to the user.
|
|
212
|
+
|
|
213
|
+
4. REAL YOUTUBE & MEDIA LINKS (WHEN DISCUSSING MEDIA):
|
|
214
|
+
- Provide REAL, WORKING, CLICKABLE links in Markdown format:
|
|
215
|
+
• Direct Watch Link: [Watch on YouTube](https://www.youtube.com/watch?v=VIDEO_ID)
|
|
216
|
+
• Search Link: https://www.youtube.com/results?search_query=QUERY
|
|
217
|
+
- NEVER use fake placeholder links like "watch?v=xxx".
|
|
218
|
+
|
|
219
|
+
5. SMART TOOL SELECTION (USE TOOLS TO GATHER FACTS, THEN ANSWER):
|
|
220
|
+
- Music videos & stages ➔ get_music_videos or get_media_links
|
|
221
|
+
- Songs, OSTs, Singles ➔ search_discography or get_media_links
|
|
222
|
+
- Drama works & trailers ➔ search_works or get_work_detail
|
|
223
|
+
- Profile & fandom ➔ get_profile_and_fandom
|
|
224
|
+
- Brand endorsements ➔ get_brand_endorsements
|
|
225
|
+
- Breaking news & external facts ➔ web_search or web_extract (preferred for multi-source research)
|
|
226
|
+
- Deep research on a topic ➔ web_extract (search + auto-fetch top results)
|
|
227
|
+
- Read a specific webpage ➔ fetch_webpage (cheerio-powered extraction)
|
|
228
|
+
- Fetch REST API / JSON data ➔ fetch_json
|
|
229
|
+
- Read local project files ➔ read_local_file
|
|
230
|
+
- Browse directory structure ➔ list_directory
|
|
231
|
+
- Visual screenshot ➔ screenshot_webpage
|
|
232
|
+
- If local tools return insufficient results, ALWAYS fall back to web_search or web_extract.
|
|
233
|
+
|
|
234
|
+
6. FACT RETRIEVAL & CHRONOLOGICAL ACCURACY:
|
|
235
|
+
- THE CURRENT YEAR IS 2026.
|
|
236
|
+
- Always use tools to verify facts before answering. Do NOT hallucinate dates, titles, or links.
|
|
237
|
+
- Sort chronologically (newest first). Never repeat the same statement.
|
|
238
|
+
- Ju Jingyi's key 2026 projects:
|
|
239
|
+
• "Veil of Shadows (月鳞绮纪)" — Role: Lu Wuyi (露芜衣) on Youku
|
|
240
|
+
• "The Eternal Fragrance (千香)" — Role: Jiang Lifei (姜黎非) with Song Weilong on Youku
|
|
241
|
+
• "Wanhua Sin (万华世界)" — Promo OST: "Set Fire to the Silent World (着火的沉默世界)"
|
|
242
|
+
• 2026 Solo Single: "FIND THE WAY" (YouTube: 9AeErG03SZw)
|
|
243
|
+
• 2026 Special Live Stage: "Yu Ai / 雨爱" (YouTube: OMLpNyp8T7Q)
|
|
244
|
+
- Classic hits: "In Blossom (2024)", "Sword and Fairy 4 (2024)", "Beauty of Resilience (2023)", "Rebirth for You (2021)", "The Blooms at Ruyi Pavilion (2020)", "The Legend of White Snake (2019)", "Legend of Yunxi (2018)".
|
|
245
|
+
|
|
246
|
+
7. COMPREHENSIVE KNOWLEDGE — LEGAL & CAREER DETAILS:
|
|
247
|
+
When discussing Ju Jingyi's career or controversies, be accurate and specific:
|
|
248
|
+
- CAREER PATH: SNH48 2nd gen (2013) → "4000-year idol" nickname (2014) → General Election winner 2 consecutive years (2016-2017) → Star Hall of Fame → Solo career → Acting breakthrough with "Legend of Yunxi" (2018).
|
|
249
|
+
- CONTRACT DISPUTE (Star48/Siba Media): The supplementary agreement dispute. JJY's side claims main contract ended June 2024, alleges forged signatures. Star48 claims contract extends to 2033. Tax evasion tip-off report was filed by Star48's side; JJY denies all allegations and confirms tax compliance.
|
|
250
|
+
- PORTRAIT/COPYRIGHT INFRINGEMENT CASES: Ju Jingyi has filed lawsuits against beauty clinics and brands for unauthorized use of her image/likeness (Right of Portrait / 肖像权纠纷). This is a separate legal matter from the Star48 dispute.
|
|
251
|
+
- When asked about legal issues, use web_search or web_extract to get the latest verified information.
|
|
252
|
+
|
|
253
|
+
8. CONCISE & SMART RESPONSE QUALITY:
|
|
254
|
+
- Match the depth of your answer to what the user asked.
|
|
255
|
+
- Do not dump the entire database unless specifically requested.
|
|
256
|
+
- Start directly with the answer — no template greetings like "Hello!" or "I searched...".
|
|
257
|
+
- Keep the interface visually restrained; omit emoji unless one clearly improves recognition of the topic.
|
|
258
|
+
|
|
259
|
+
9. PROACTIVE MUSIC INVITATION:
|
|
260
|
+
- When discussing a specific song/MV/OST, naturally ask if the user wants to listen.
|
|
261
|
+
- Example: "เพลง FIND THE WAY จังหวะทรงพลังมากเลยค่ะ! อยากให้ Kiku เปิดให้ฟังมั้ยคะ?"
|
|
262
|
+
- If user agrees → call play_music. If user says "Stop" → call stop_music.`,
|
|
263
|
+
}
|
|
264
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export function estimateTokenCount(textOrMessages) {
|
|
2
|
+
if (!textOrMessages) return 0;
|
|
3
|
+
if (typeof textOrMessages === 'string') {
|
|
4
|
+
return Math.ceil(textOrMessages.length / 3.5);
|
|
5
|
+
}
|
|
6
|
+
if (Array.isArray(textOrMessages)) {
|
|
7
|
+
let chars = 0;
|
|
8
|
+
for (const msg of textOrMessages) {
|
|
9
|
+
if (typeof msg.content === 'string') chars += msg.content.length;
|
|
10
|
+
if (msg.tool_calls) chars += JSON.stringify(msg.tool_calls).length;
|
|
11
|
+
}
|
|
12
|
+
return Math.ceil(chars / 3.5);
|
|
13
|
+
}
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ContextCompactor {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.maxTokens = options.maxTokens || 6000;
|
|
20
|
+
this.keepRecentMessages = options.keepRecentMessages || 6;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
summarizeHistorySync(messages = []) {
|
|
24
|
+
if (!messages || messages.length === 0) return '';
|
|
25
|
+
const points = [];
|
|
26
|
+
for (const msg of messages) {
|
|
27
|
+
if (msg.role === 'user' && typeof msg.content === 'string' && msg.content.trim()) {
|
|
28
|
+
const text = msg.content.slice(0, 100).replace(/\n+/g, ' ').trim();
|
|
29
|
+
points.push(`User asked: "${text}"`);
|
|
30
|
+
} else if (msg.role === 'assistant' && typeof msg.content === 'string' && msg.content.trim()) {
|
|
31
|
+
const text = msg.content.slice(0, 120).replace(/\n+/g, ' ').trim();
|
|
32
|
+
points.push(`Assistant covered: "${text}"`);
|
|
33
|
+
} else if (msg.role === 'tool') {
|
|
34
|
+
const toolName = msg.name || 'tool';
|
|
35
|
+
points.push(`Verified via ${toolName}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return points.slice(-8).join(' | ');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
compact(conversationHistory = [], options = {}) {
|
|
42
|
+
if (!Array.isArray(conversationHistory) || conversationHistory.length <= this.keepRecentMessages + 1) {
|
|
43
|
+
return conversationHistory;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const currentTokens = estimateTokenCount(conversationHistory);
|
|
47
|
+
const maxTokens = options.maxTokens || this.maxTokens;
|
|
48
|
+
const keepRecent = options.keepRecentMessages || this.keepRecentMessages;
|
|
49
|
+
|
|
50
|
+
if (currentTokens <= maxTokens && conversationHistory.length <= keepRecent + 2) {
|
|
51
|
+
return conversationHistory;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const systemMessage = conversationHistory[0]?.role === 'system' ? conversationHistory[0] : null;
|
|
55
|
+
const nonSystemMessages = systemMessage ? conversationHistory.slice(1) : conversationHistory;
|
|
56
|
+
|
|
57
|
+
if (nonSystemMessages.length <= keepRecent) {
|
|
58
|
+
return conversationHistory;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const olderMessages = nonSystemMessages.slice(0, -keepRecent);
|
|
62
|
+
const recentMessages = nonSystemMessages.slice(-keepRecent);
|
|
63
|
+
|
|
64
|
+
const summaryText = this.summarizeHistorySync(olderMessages);
|
|
65
|
+
const summaryContextMessage = {
|
|
66
|
+
role: 'system',
|
|
67
|
+
content: `[CONVERSATION CONTEXT SUMMARY OF EARLIER MESSAGES: ${summaryText}]`
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const compacted = [];
|
|
71
|
+
if (systemMessage) compacted.push(systemMessage);
|
|
72
|
+
if (summaryText) compacted.push(summaryContextMessage);
|
|
73
|
+
compacted.push(...recentMessages);
|
|
74
|
+
|
|
75
|
+
return compacted;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const contextCompactor = new ContextCompactor();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { KIKU_DATA_DIR } from './appPaths.js';
|
|
5
|
+
|
|
6
|
+
async function optionalImport(name) {
|
|
7
|
+
try { await import(name); return true; } catch { return false; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function commandAvailable(command, args = ['--version']) {
|
|
11
|
+
return new Promise(resolve => {
|
|
12
|
+
let settled = false;
|
|
13
|
+
const child = spawn(command, args, { stdio: 'ignore', windowsHide: true });
|
|
14
|
+
const finish = available => {
|
|
15
|
+
if (settled) return;
|
|
16
|
+
settled = true;
|
|
17
|
+
clearTimeout(timer);
|
|
18
|
+
resolve(available);
|
|
19
|
+
};
|
|
20
|
+
const timer = setTimeout(() => {
|
|
21
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
22
|
+
finish(false);
|
|
23
|
+
}, 3000);
|
|
24
|
+
child.once('error', () => finish(false));
|
|
25
|
+
child.once('close', code => finish(code === 0));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function checkDataDirectory() {
|
|
30
|
+
const probe = path.join(KIKU_DATA_DIR, `.write-test-${process.pid}`);
|
|
31
|
+
try {
|
|
32
|
+
await fs.mkdir(KIKU_DATA_DIR, { recursive: true, mode: 0o700 });
|
|
33
|
+
await fs.writeFile(probe, 'ok', { mode: 0o600 });
|
|
34
|
+
await fs.unlink(probe);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
import { getGpuMetrics } from './gpuMetrics.js';
|
|
42
|
+
|
|
43
|
+
export async function runDoctor(options = {}) {
|
|
44
|
+
const client = options.client;
|
|
45
|
+
const [dataWritable, speaker, ffmpegStatic, fluentFfmpeg, ytDlp, lm, gpu] = await Promise.all([
|
|
46
|
+
checkDataDirectory(),
|
|
47
|
+
optionalImport('speaker'),
|
|
48
|
+
optionalImport('ffmpeg-static'),
|
|
49
|
+
optionalImport('fluent-ffmpeg'),
|
|
50
|
+
commandAvailable(process.env.KIKU_YTDLP || 'yt-dlp'),
|
|
51
|
+
client ? client.checkConnection() : Promise.resolve({ connected: false, models: [] }),
|
|
52
|
+
getGpuMetrics().catch(() => ({ available: false })),
|
|
53
|
+
]);
|
|
54
|
+
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
55
|
+
const checks = [
|
|
56
|
+
{ id: 'node', required: true, ok: nodeMajor >= 20, detail: `Node ${process.versions.node} (requires >=20)` },
|
|
57
|
+
{ id: 'data', required: true, ok: dataWritable, detail: KIKU_DATA_DIR },
|
|
58
|
+
{ id: 'gpu', required: false, ok: Boolean(gpu.available), detail: gpu.available && gpu.primary ? `${gpu.primary.name} (${gpu.primary.totalGb} GB VRAM${gpu.primary.driverVersion !== 'N/A' ? `, Driver ${gpu.primary.driverVersion}` : ''})` : 'CPU fallback / no dedicated acceleration detected' },
|
|
59
|
+
{ id: 'lmstudio', required: false, ok: Boolean(lm.connected), detail: lm.connected ? `${lm.models?.length || 0} loaded model(s)` : 'offline; catalog-only mode is available' },
|
|
60
|
+
{ id: 'ffmpeg', required: false, ok: ffmpegStatic && fluentFfmpeg, detail: 'audio add-on only; intentionally not installed with the core CLI' },
|
|
61
|
+
{ id: 'speaker', required: false, ok: speaker, detail: 'native audio add-on only; intentionally not installed with the core CLI' },
|
|
62
|
+
{ id: 'yt-dlp', required: false, ok: ytDlp, detail: 'recommended for YouTube search and playback' },
|
|
63
|
+
];
|
|
64
|
+
return {
|
|
65
|
+
ok: checks.every(check => !check.required || check.ok),
|
|
66
|
+
checks,
|
|
67
|
+
dataDir: KIKU_DATA_DIR,
|
|
68
|
+
};
|
|
69
|
+
}
|