kiku-agent 1.0.11 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -495
- package/package.json +12 -11
- package/public/fonts/GoogleSans-Bold.ttf +0 -0
- package/public/fonts/GoogleSans-Bold.woff2 +0 -0
- package/public/fonts/GoogleSans-BoldItalic.ttf +0 -0
- package/public/fonts/GoogleSans-Italic.ttf +0 -0
- package/public/fonts/GoogleSans-Latin-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-700.woff2 +0 -0
- package/public/fonts/GoogleSans-Medium.ttf +0 -0
- package/public/fonts/GoogleSans-Medium.woff2 +0 -0
- package/public/fonts/GoogleSans-Regular.ttf +0 -0
- package/public/fonts/GoogleSans-Regular.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-700.woff2 +0 -0
- package/public/fonts/NotoSansThai-Bold.woff2 +0 -0
- package/public/fonts/NotoSansThai-Regular.woff2 +0 -0
- package/public/fonts/google-sans-official.css +900 -0
- package/public/images/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-399915.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-424041.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-588166.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-672198.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-700522.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-839722.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026825-460586.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/263202694-689407.jpg +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-399915.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-424041.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-588166.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-672198.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-700522.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-839722.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026825-460586.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/263202694-689407.mp4 +0 -0
- package/public/images/general/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/general/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/general/jujingyi_portrait.jpg +0 -0
- package/public/images/jujingyi_portrait.jpg +0 -0
- package/public/images/series/a-millenniums-wait/poster.jpg +0 -0
- package/public/images/series/beauty-of-resilience/poster.jpg +0 -0
- package/public/images/series/detective-samoyeds/poster.jpg +0 -0
- package/public/images/series/everyday/poster.jpg +0 -0
- package/public/images/series/fu-rong-ancient-painting/poster.jpg +0 -0
- package/public/images/series/in-a-class-of-her-own/poster.jpg +0 -0
- package/public/images/series/in-blossom/poster.jpg +0 -0
- package/public/images/series/legend-of-yunxi/poster.jpg +0 -0
- package/public/images/series/lets-fight/poster-2.jpg +0 -0
- package/public/images/series/lets-fight/poster.jpg +0 -0
- package/public/images/series/love-emergency/poster.jpg +0 -0
- package/public/images/series/love-under-the-full-moon/poster.jpg +0 -0
- package/public/images/series/money-bastion/poster.jpg +0 -0
- package/public/images/series/mr-swimmer/poster.jpg +0 -0
- package/public/images/series/novoland-castle-in-the-sky/poster.jpg +0 -0
- package/public/images/series/please-give-me-a-pair-of-wings/poster.jpg +0 -0
- package/public/images/series/rebirth-for-you/poster.jpg +0 -0
- package/public/images/series/sword-and-fairy-4/poster.jpg +0 -0
- package/public/images/series/the-blooms-at-ruyi-pavilion/poster.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster-2.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster.jpg +0 -0
- package/public/images/series/the-fallen-flowers/poster.jpg +0 -0
- package/public/images/series/the-legend-of-white-snake/poster.jpg +0 -0
- package/public/images/series/veil-of-shadows/poster.jpg +0 -0
- package/public/images/series/wanhua-sin/poster-2.jpg +0 -0
- package/public/images/series/wanhua-sin/poster.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj10yogj62u03s0npe02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj2pdvyj61li25jkjl02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj71177j62672w9e8302.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj7twpfj62192197rw02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj8jfu8j62c03401kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj9dhdsj622k2l77wh02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkja31s4j62bw3407r802.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjcf331j61402ey1kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjdx8gqj62602pihdt02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjfvxnzj62432n57wh02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixniv2yj627y4fxb2b02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixsvbdaj625k2vfu1102.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixxxdaoj62c03501l202.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixzeef9j62c0340npd02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy0vs6tj626t4rbhdu02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy21wwuj62bt34hx6q02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly4wr85j62z629y7wh02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly67oilj623u2t5npd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly9qtw8j62c0340u0x02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyem4m4j63402c01kz02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlygkoq4j627s2yeavd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyjh0daj62bw340nm102.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlylejlhj62bw3401kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaa8b7fj627j2y67wh02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiapyartj62bw340hdt02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaibkwu4ij61vy2iq1kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic1fvkpj61u92eo1ky02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic3cruhj62822ywb2902.jpg +0 -0
- package/public/manifest.json +25 -0
- package/public/mcp-logo-128.png +0 -0
- package/public/mcp-logo-64.png +0 -0
- package/public/mcp-logo.svg +16 -0
- package/server/agent/agentCore.js +542 -74
- package/server/agent/appPaths.js +1 -0
- package/server/agent/audioPlayer.js +4 -2
- package/server/agent/citations.js +294 -0
- package/server/agent/clarificationAnalytics.js +477 -0
- package/server/agent/clarifyingQuestions.js +1089 -0
- package/server/agent/cli.js +549 -125
- package/server/agent/commandFeatures.js +1 -0
- package/server/agent/commandRegistry.js +946 -879
- package/server/agent/commands.js +546 -272
- package/server/agent/config.js +581 -412
- package/server/agent/contextCompactor.js +2 -2
- package/server/agent/debate.js +235 -0
- package/server/agent/eventBus.js +3 -0
- package/server/agent/feedbackLearning.js +217 -0
- package/server/agent/llmClient.js +512 -394
- package/server/agent/mcpClient.js +182 -32
- package/server/agent/memory.js +283 -224
- package/server/agent/modelManager.js +79 -39
- package/server/agent/profiles.js +178 -0
- package/server/agent/publicTools.js +22 -0
- package/server/agent/queryDetails.js +68 -8
- package/server/agent/quizData.js +398 -384
- package/server/agent/research.js +121 -79
- package/server/agent/sanitizer.js +104 -13
- package/server/agent/session.js +319 -11
- package/server/agent/subagent.js +8 -3
- package/server/agent/suggestionTui.js +294 -0
- package/server/agent/suggestions.js +69 -3
- package/server/agent/telemetry.js +119 -0
- package/server/agent/terminalImage.js +146 -27
- package/server/agent/theme.js +17 -0
- package/server/agent/toolRouter.js +16 -1
- package/server/agent/tools.js +424 -225
- package/server/agent/ui.js +486 -64
- package/server/agent/version.js +2 -4
- package/server/agent/webSearch.js +111 -11
- package/server/agent/youtubeAuth.js +24 -6
- package/server/apiRouter.js +4222 -0
- package/server/mcp/data/discography.js +25 -16
- package/server/mcp/data/gallery.js +308 -20
- package/server/mcp/data/profile.js +74 -44
- package/server/mcp/data/timeline.js +1 -1
- package/server/mcp/data/works.js +51 -17
- package/server/mcp/dataAdapter.js +5 -3
- package/server/mcp/handler.js +5 -11
- package/server/mcp/prompts.js +86 -0
- package/server/mcp/security.js +18 -15
- package/server/mcp/server.js +7 -2
- package/server/mcp/tools.js +66 -2
- package/server/web-chat/core/browserLauncher.js +18 -0
- package/server/web-chat/core/connection.js +194 -0
- package/server/web-chat/core/conversationContext.js +82 -0
- package/server/web-chat/core/directCatalogEngine.js +196 -0
- package/server/web-chat/core/dramaKnowledge.js +142 -0
- package/server/web-chat/core/errorHandler.js +51 -0
- package/server/web-chat/core/followUpEngine.js +182 -0
- package/server/web-chat/core/futureEventGuard.js +80 -0
- package/server/web-chat/core/memorySnapshot.js +12 -0
- package/server/web-chat/core/security.js +146 -0
- package/server/web-chat/core/toolInspector.js +52 -0
- package/server/web-chat/router.js +120 -0
- package/server/web-chat/routes/chatRoute.js +397 -0
- package/server/web-chat/routes/clarifyConfigRoute.js +98 -0
- package/server/web-chat/routes/clarifyRoute.js +83 -0
- package/server/web-chat/routes/compareRoute.js +79 -0
- package/server/web-chat/routes/exportRoute.js +59 -0
- package/server/web-chat/routes/feedbackRoute.js +57 -0
- package/server/web-chat/routes/newsRoute.js +33 -0
- package/server/web-chat/routes/quizRoute.js +11 -0
- package/server/web-chat/routes/staticRoute.js +103 -0
- package/server/web-chat/routes/statusRoute.js +52 -0
- package/server/web-chat/routes/suggestRoute.js +77 -0
- package/server/web-chat/routes/timelineRoute.js +34 -0
- package/server/web-chat/routes/verifyRoute.js +57 -0
- package/server/web-chat/server.js +116 -0
- package/server/web-chat/ui/htmlBuilder.js +91 -0
- package/server/web-chat/ui/previews/agentBubble.preview.html +134 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +320 -0
- package/server/web-chat/ui/scripts/client.js +8397 -0
- package/server/web-chat/ui/scripts/kikuDb.js +177 -0
- package/server/web-chat/ui/scripts/suggestionsData.js +118 -0
- package/server/web-chat/ui/scripts/webchatI18n.js +291 -0
- package/server/web-chat/ui/styles/main.css +8568 -0
- package/server/web-chat/ui/templates/body.html +796 -0
- package/server/webChatHtml.js +1 -0
- package/server/webChatServer.js +6 -0
- package/src/data/discographyData.js +892 -866
- package/src/data/galleryData.js +1805 -561
- package/src/data/profileData.js +79 -2
- package/src/data/socialUpdatesData.js +106 -47
- package/src/data/timelineData.js +1 -1
- package/src/data/worksData.js +25 -4
- package/tokens.css +97 -0
- package/server/agent/updateManager.js +0 -456
package/server/agent/config.js
CHANGED
|
@@ -1,271 +1,424 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { PROVIDER_KEYS_FILE, SETTINGS_FILE } from './appPaths.js';
|
|
5
|
+
|
|
6
|
+
const PROVIDER_MODEL_ALIASES = {
|
|
7
|
+
lmstudio: {
|
|
8
|
+
'q8': 'google/gemma-4-e4b-Q8_0',
|
|
9
|
+
'q8_0': 'google/gemma-4-e4b-Q8_0',
|
|
10
|
+
'q8-0': 'google/gemma-4-e4b-Q8_0',
|
|
11
|
+
'8bit': 'google/gemma-4-e4b-Q8_0',
|
|
12
|
+
'8-bit': 'google/gemma-4-e4b-Q8_0',
|
|
13
|
+
'gemma-q8': 'google/gemma-4-e4b-Q8_0',
|
|
14
|
+
'gemma-q8_0': 'google/gemma-4-e4b-Q8_0',
|
|
15
|
+
'gemma-4-e4b-q8': 'google/gemma-4-e4b-Q8_0',
|
|
16
|
+
'gemma-4-e4b-q8_0': 'google/gemma-4-e4b-Q8_0',
|
|
17
|
+
'google/gemma-4-e4b-q8_0': 'google/gemma-4-e4b-Q8_0',
|
|
18
|
+
'google/gemma-4-e4b-q8': 'google/gemma-4-e4b-Q8_0',
|
|
19
|
+
|
|
20
|
+
'q6': 'google/gemma-4-e4b-Q6_K',
|
|
21
|
+
'q6_k': 'google/gemma-4-e4b-Q6_K',
|
|
22
|
+
'q6-k': 'google/gemma-4-e4b-Q6_K',
|
|
23
|
+
'6bit': 'google/gemma-4-e4b-Q6_K',
|
|
24
|
+
'6-bit': 'google/gemma-4-e4b-Q6_K',
|
|
25
|
+
'gemma-q6': 'google/gemma-4-e4b-Q6_K',
|
|
26
|
+
'gemma-q6_k': 'google/gemma-4-e4b-Q6_K',
|
|
27
|
+
'gemma-4-e4b-q6': 'google/gemma-4-e4b-Q6_K',
|
|
28
|
+
'gemma-4-e4b-q6_k': 'google/gemma-4-e4b-Q6_K',
|
|
29
|
+
'google/gemma-4-e4b-q6_k': 'google/gemma-4-e4b-Q6_K',
|
|
30
|
+
'google/gemma-4-e4b-q6': 'google/gemma-4-e4b-Q6_K',
|
|
31
|
+
|
|
32
|
+
'q4': 'google/gemma-4-e4b-Q4_K_M',
|
|
33
|
+
'q4_k': 'google/gemma-4-e4b-Q4_K_M',
|
|
34
|
+
'q4_k_m': 'google/gemma-4-e4b-Q4_K_M',
|
|
35
|
+
'q4-k-m': 'google/gemma-4-e4b-Q4_K_M',
|
|
36
|
+
'4bit': 'google/gemma-4-e4b-Q4_K_M',
|
|
37
|
+
'4-bit': 'google/gemma-4-e4b-Q4_K_M',
|
|
38
|
+
'gemma-q4': 'google/gemma-4-e4b-Q4_K_M',
|
|
39
|
+
'gemma-q4_k_m': 'google/gemma-4-e4b-Q4_K_M',
|
|
40
|
+
'gemma-4-e4b-q4': 'google/gemma-4-e4b-Q4_K_M',
|
|
41
|
+
'gemma-4-e4b-q4_k_m': 'google/gemma-4-e4b-Q4_K_M',
|
|
42
|
+
'google/gemma-4-e4b-q4_k_m': 'google/gemma-4-e4b-Q4_K_M',
|
|
43
|
+
'google/gemma-4-e4b-q4': 'google/gemma-4-e4b-Q4_K_M',
|
|
44
|
+
|
|
45
|
+
'gemma': 'google/gemma-4-e4b',
|
|
46
|
+
'gemma4': 'google/gemma-4-e4b',
|
|
47
|
+
'gemma-4': 'google/gemma-4-e4b',
|
|
48
|
+
'gemma-4-e4b': 'google/gemma-4-e4b',
|
|
49
|
+
},
|
|
50
|
+
openai: {
|
|
51
|
+
'o1-mini': 'gpt-5.6-luna',
|
|
52
|
+
'o3-mini': 'gpt-5.6-luna',
|
|
53
|
+
},
|
|
54
|
+
openrouter: {
|
|
55
|
+
auto: 'openrouter/auto',
|
|
56
|
+
},
|
|
57
|
+
groq: {
|
|
58
|
+
'gpt-oss-120b': 'openai/gpt-oss-120b',
|
|
59
|
+
'gpt-oss-20b': 'openai/gpt-oss-20b',
|
|
60
|
+
},
|
|
61
|
+
deepseek: {
|
|
62
|
+
'deepseek-chat': 'deepseek-v4-flash',
|
|
63
|
+
'deepseek-reasoner': 'deepseek-v4-flash',
|
|
64
|
+
},
|
|
65
|
+
gemini: {
|
|
66
|
+
'gemini-3.1-pro': 'gemini-3.1-pro-preview',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const GENERIC_MODEL_TOKENS = new Set(['model', 'chat', 'default', 'base', 'instruct', 'text', 'embedding']);
|
|
71
|
+
|
|
72
|
+
export function extractModelQuantization(modelId) {
|
|
73
|
+
if (!modelId || typeof modelId !== 'string') return null;
|
|
74
|
+
const clean = modelId.toUpperCase();
|
|
75
|
+
const m = clean.match(/(Q[0-9]+_[A-Z0-9_]+|Q[0-9]+_[A-Z]|Q[0-9]+|[0-9]+BIT|FP16|FP32|BF16)/i);
|
|
76
|
+
return m ? m[1].toUpperCase() : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function cleanModelName(modelId) {
|
|
80
|
+
if (!modelId || typeof modelId !== 'string') return '';
|
|
81
|
+
let name = modelId.trim();
|
|
82
|
+
name = name.replace(/\.(gguf|bin|safetensors)$/i, '');
|
|
83
|
+
if (name.includes('/')) {
|
|
84
|
+
const parts = name.split('/').filter(Boolean);
|
|
85
|
+
name = parts[parts.length - 1] || parts[0];
|
|
86
|
+
}
|
|
87
|
+
return name.toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function matchModelIdentifier(target, candidate) {
|
|
91
|
+
if (!target || !candidate) return false;
|
|
92
|
+
if (target === candidate) return true;
|
|
93
|
+
|
|
94
|
+
const targetLower = String(target).toLowerCase().trim();
|
|
95
|
+
const candLower = String(candidate).toLowerCase().trim();
|
|
96
|
+
if (targetLower === candLower) return true;
|
|
97
|
+
|
|
98
|
+
const targetClean = cleanModelName(target);
|
|
99
|
+
const candClean = cleanModelName(candidate);
|
|
100
|
+
if (targetClean && candClean && targetClean === candClean) return true;
|
|
101
|
+
|
|
102
|
+
const targetQuant = extractModelQuantization(target);
|
|
103
|
+
const candQuant = extractModelQuantization(candidate);
|
|
104
|
+
|
|
105
|
+
const targetIsGemma = /gemma/i.test(target);
|
|
106
|
+
const candIsGemma = /gemma/i.test(candidate);
|
|
107
|
+
|
|
108
|
+
if (candIsGemma) {
|
|
109
|
+
if (targetIsGemma) {
|
|
110
|
+
if (targetQuant && candQuant) {
|
|
111
|
+
const normTargetQ = targetQuant.replace(/[^A-Z0-9]/g, '');
|
|
112
|
+
const normCandQ = candQuant.replace(/[^A-Z0-9]/g, '');
|
|
113
|
+
if (normTargetQ === normCandQ || normCandQ.startsWith(normTargetQ) || normTargetQ.startsWith(normCandQ)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
} else if (!targetQuant) {
|
|
117
|
+
if (targetClean.includes('gemma') && candClean.includes('gemma')) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else if (targetQuant && !GENERIC_MODEL_TOKENS.has(targetClean)) {
|
|
122
|
+
if (candQuant) {
|
|
123
|
+
const normTargetQ = targetQuant.replace(/[^A-Z0-9]/g, '');
|
|
124
|
+
const normCandQ = candQuant.replace(/[^A-Z0-9]/g, '');
|
|
125
|
+
if (normTargetQ === normCandQ || normCandQ.startsWith(normTargetQ) || normTargetQ.startsWith(normCandQ)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (targetClean && candClean && !GENERIC_MODEL_TOKENS.has(targetClean) && targetClean.length >= 5) {
|
|
133
|
+
if (candClean.includes(targetClean) || (candClean.length >= 5 && targetClean.includes(candClean))) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function normalizeProviderModel(providerId, modelId) {
|
|
142
|
+
const normalizedProvider = String(providerId || '').toLowerCase().trim();
|
|
143
|
+
const normalizedModel = String(modelId || '').trim();
|
|
144
|
+
const aliasMatch = PROVIDER_MODEL_ALIASES[normalizedProvider]?.[normalizedModel.toLowerCase()]
|
|
145
|
+
|| PROVIDER_MODEL_ALIASES[normalizedProvider]?.[normalizedModel];
|
|
146
|
+
return aliasMatch || normalizedModel;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const CLOUD_PROVIDERS = {
|
|
150
|
+
lmstudio: {
|
|
151
|
+
id: 'lmstudio',
|
|
152
|
+
name: 'LM Studio (Local)',
|
|
153
|
+
baseUrl: process.env.LMSTUDIO_BASE_URL || process.env.OPENAI_BASE_URL || 'http://127.0.0.1:1234/v1',
|
|
154
|
+
apiKey: process.env.LMSTUDIO_API_KEY || 'lm-studio',
|
|
155
|
+
defaultModel: 'google/gemma-4-e4b',
|
|
156
|
+
isLocal: true,
|
|
157
|
+
envKey: 'LMSTUDIO_BASE_URL',
|
|
158
|
+
docUrl: 'https://lmstudio.ai/docs',
|
|
159
|
+
apiRefUrl: 'https://lmstudio.ai/docs/api/local-server',
|
|
160
|
+
pricingSummary: '100% Free / Local (No API Key required)',
|
|
161
|
+
features: ['local', 'private', 'offline', 'zero-cost', 'openai-compatible'],
|
|
162
|
+
recommendedModels: [
|
|
163
|
+
{ 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.' },
|
|
164
|
+
{ 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).' },
|
|
165
|
+
{ 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).' },
|
|
166
|
+
{ 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).' },
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
openai: {
|
|
170
|
+
id: 'openai',
|
|
171
|
+
name: 'OpenAI Cloud',
|
|
172
|
+
baseUrl: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
|
|
173
|
+
apiKey: process.env.OPENAI_API_KEY || '',
|
|
174
|
+
defaultModel: normalizeProviderModel('openai', process.env.OPENAI_MODEL || 'gpt-5.6-luna'),
|
|
175
|
+
isLocal: false,
|
|
176
|
+
envKey: 'OPENAI_API_KEY',
|
|
177
|
+
docUrl: 'https://developers.openai.com/api/docs/models',
|
|
178
|
+
apiRefUrl: 'https://developers.openai.com/api/reference',
|
|
179
|
+
pricingSummary: 'GPT-5.6 Luna: $0.20/1M in, $1.20/1M out; Terra/Sol available for higher capability',
|
|
180
|
+
features: ['tools', 'streaming', 'vision', 'reasoning', 'system-prompt'],
|
|
181
|
+
recommendedModels: [
|
|
182
|
+
{ id: 'gpt-5.6-luna', name: 'GPT-5.6 Luna', tier: 'cost-sensitive', cost: '$0.20 / 1M in', context: '1.05M', hasTools: true, description: 'Recommended default for efficient, high-volume multilingual agent workloads.' },
|
|
183
|
+
{ id: 'gpt-5.6-terra', name: 'GPT-5.6 Terra', tier: 'balanced', cost: '$2.00 / 1M in', context: '1.05M', hasTools: true, description: 'Balances intelligence and cost for production reasoning and tool use.' },
|
|
184
|
+
{ id: 'gpt-5.6-sol', name: 'GPT-5.6 Sol', tier: 'flagship', cost: '$4.00 / 1M in', context: '1.05M', hasTools: true, description: 'Flagship model for complex professional reasoning and coding.' },
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
openrouter: {
|
|
188
|
+
id: 'openrouter',
|
|
189
|
+
name: 'OpenRouter Cloud',
|
|
190
|
+
baseUrl: process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1',
|
|
191
|
+
apiKey: process.env.OPENROUTER_API_KEY || '',
|
|
192
|
+
defaultModel: normalizeProviderModel('openrouter', process.env.OPENROUTER_MODEL || 'openrouter/auto'),
|
|
193
|
+
extraHeaders: {
|
|
194
|
+
'HTTP-Referer': 'https://jujingyi-kikuuu.vercel.app/',
|
|
195
|
+
'X-OpenRouter-Title': 'Kiku Agent',
|
|
196
|
+
},
|
|
197
|
+
isLocal: false,
|
|
198
|
+
envKey: 'OPENROUTER_API_KEY',
|
|
199
|
+
docUrl: 'https://openrouter.ai/docs/guides/routing/routers/auto-router',
|
|
200
|
+
apiRefUrl: 'https://openrouter.ai/docs/api/reference/overview',
|
|
201
|
+
pricingSummary: 'Auto Router selects a current model dynamically; usage is billed at the routed model rate',
|
|
202
|
+
features: ['universal-routing', 'tools', 'streaming', 'free-tier', 'fallback'],
|
|
203
|
+
recommendedModels: [
|
|
204
|
+
{ id: 'openrouter/auto', name: 'OpenRouter Auto Router', tier: 'dynamic', cost: 'Routed model rate', context: 'Model-dependent', hasTools: true, description: 'Recommended default. Chooses from the current catalog so a retired third-party model does not break startup.' },
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
groq: {
|
|
208
|
+
id: 'groq',
|
|
209
|
+
name: 'Groq Cloud',
|
|
210
|
+
baseUrl: process.env.GROQ_BASE_URL || 'https://api.groq.com/openai/v1',
|
|
211
|
+
apiKey: process.env.GROQ_API_KEY || '',
|
|
212
|
+
defaultModel: normalizeProviderModel('groq', process.env.GROQ_MODEL || 'openai/gpt-oss-120b'),
|
|
213
|
+
isLocal: false,
|
|
214
|
+
envKey: 'GROQ_API_KEY',
|
|
215
|
+
docUrl: 'https://console.groq.com/docs/quickstart',
|
|
216
|
+
apiRefUrl: 'https://console.groq.com/docs/api-reference',
|
|
217
|
+
pricingSummary: 'Ultra-fast LPU inference (GPT-OSS 20B: $0.075/1M in, GPT-OSS 120B: $0.15/1M in)',
|
|
218
|
+
features: ['lpu-speed', 'tools', 'streaming', 'openai-compatible'],
|
|
219
|
+
recommendedModels: [
|
|
220
|
+
{ id: 'openai/gpt-oss-120b', name: 'GPT-OSS 120B', tier: 'flagship', cost: '$0.15 / 1M in', context: '131k', hasTools: true, description: 'Recommended default. Production-grade 120B reasoning model with tool use on Groq.' },
|
|
221
|
+
{ id: 'openai/gpt-oss-20b', name: 'GPT-OSS 20B', tier: 'ultra-cheap', cost: '$0.075 / 1M in', context: '131k', hasTools: true, description: 'Fast production model for lower-cost, high-volume requests.' },
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
deepseek: {
|
|
225
|
+
id: 'deepseek',
|
|
226
|
+
name: 'DeepSeek Cloud',
|
|
227
|
+
baseUrl: process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com',
|
|
228
|
+
apiKey: process.env.DEEPSEEK_API_KEY || '',
|
|
229
|
+
defaultModel: normalizeProviderModel('deepseek', process.env.DEEPSEEK_MODEL || 'deepseek-v4-flash'),
|
|
230
|
+
isLocal: false,
|
|
231
|
+
envKey: 'DEEPSEEK_API_KEY',
|
|
232
|
+
docUrl: 'https://api-docs.deepseek.com',
|
|
233
|
+
apiRefUrl: 'https://api-docs.deepseek.com/api/create-chat-completion',
|
|
234
|
+
pricingSummary: 'V4 Flash: $0.22-$0.44/1M uncached input; V4 Pro: $0.66-$1.32/1M (off-peak/peak)',
|
|
235
|
+
features: ['deepseek-v4', 'thinking', 'tools', 'streaming', 'context-cache', '1m-context'],
|
|
236
|
+
recommendedModels: [
|
|
237
|
+
{ id: 'deepseek-v4-flash', name: 'DeepSeek V4 Flash', tier: 'balanced', cost: '$0.22-$0.44 / 1M uncached in', context: '1M', hasTools: true, description: 'Recommended default. Current V4 model with tool calls and thinking/non-thinking modes.' },
|
|
238
|
+
{ id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro', tier: 'flagship', cost: '$0.66-$1.32 / 1M uncached in', context: '1M', hasTools: true, description: 'Higher-capability V4 model with tool calls and thinking mode.' },
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
gemini: {
|
|
242
|
+
id: 'gemini',
|
|
243
|
+
name: 'Google Gemini',
|
|
244
|
+
baseUrl: process.env.GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
245
|
+
apiKey: process.env.GEMINI_API_KEY || '',
|
|
246
|
+
defaultModel: normalizeProviderModel('gemini', process.env.GEMINI_MODEL || 'gemini-3.7-flash'),
|
|
247
|
+
isLocal: false,
|
|
248
|
+
envKey: 'GEMINI_API_KEY',
|
|
249
|
+
docUrl: 'https://ai.google.dev/gemini-api/docs',
|
|
250
|
+
apiRefUrl: 'https://ai.google.dev/api/generate-content',
|
|
251
|
+
pricingSummary: 'Gemini 3.7 Flash: free tier available; paid standard $0.75/1M input through 2026',
|
|
252
|
+
features: ['1m-context', 'multimodal', 'tools', 'streaming', 'thinking'],
|
|
253
|
+
recommendedModels: [
|
|
254
|
+
{ id: 'gemini-3.7-flash', name: 'Gemini 3.7 Flash', tier: 'latest-stable', cost: '$0.75 / 1M in (2026)', context: '1M', hasTools: true, description: 'Recommended default. Latest stable Flash model for agentic workflows and multimodal reasoning.' },
|
|
255
|
+
{ id: 'gemini-3.5-flash-lite', name: 'Gemini 3.5 Flash-Lite', tier: 'lowest-cost', cost: '$0.30 / 1M in', context: '1M', hasTools: true, description: 'Stable, cost-efficient model for high-volume translation and simple processing.' },
|
|
256
|
+
{ id: 'gemini-3.6-flash', name: 'Gemini 3.6 Flash', tier: 'stable', cost: '$0.75 / 1M in (2026)', context: '1M', hasTools: true, description: 'Stable previous-generation Flash fallback with function calling and thinking.' },
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
custom: {
|
|
260
|
+
id: 'custom',
|
|
261
|
+
name: 'Custom OpenAI-Compatible',
|
|
262
|
+
baseUrl: process.env.LLM_BASE_URL || '',
|
|
263
|
+
apiKey: process.env.LLM_API_KEY || '',
|
|
264
|
+
defaultModel: process.env.LLM_MODEL || 'default',
|
|
265
|
+
isLocal: false,
|
|
266
|
+
envKey: 'LLM_BASE_URL',
|
|
267
|
+
docUrl: 'https://platform.openai.com/docs/api-reference',
|
|
268
|
+
apiRefUrl: 'https://platform.openai.com/docs/api-reference/chat',
|
|
269
|
+
pricingSummary: 'Self-hosted or Custom Gateway',
|
|
270
|
+
features: ['custom-endpoint', 'openai-compatible', 'tools', 'streaming'],
|
|
271
|
+
recommendedModels: [
|
|
272
|
+
{ id: 'default', name: 'Default Model', tier: 'custom', cost: 'Custom', context: 'Custom', hasTools: true, description: 'Custom model specified via LLM_MODEL or /model use <id>.' },
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export function loadPersistedSettings() {
|
|
278
|
+
try {
|
|
279
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
280
|
+
const data = fs.readFileSync(SETTINGS_FILE, 'utf-8');
|
|
281
|
+
const parsed = JSON.parse(data);
|
|
282
|
+
if (parsed && typeof parsed === 'object') {
|
|
283
|
+
if (parsed.providerModels && typeof parsed.providerModels === 'object') {
|
|
284
|
+
for (const [pId, model] of Object.entries(parsed.providerModels)) {
|
|
285
|
+
const providerDef = CLOUD_PROVIDERS[pId.toLowerCase()];
|
|
286
|
+
if (providerDef && typeof model === 'string' && model.trim()) {
|
|
287
|
+
providerDef.defaultModel = model.trim();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return parsed;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch {}
|
|
295
|
+
return {};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function getPersistedActiveProvider() {
|
|
299
|
+
const settings = loadPersistedSettings();
|
|
300
|
+
if (settings.activeProvider && CLOUD_PROVIDERS[settings.activeProvider.toLowerCase()]) {
|
|
301
|
+
return settings.activeProvider.toLowerCase();
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function savePersistedActiveProvider(providerId, model = null) {
|
|
307
|
+
const pId = String(providerId || '').toLowerCase().trim();
|
|
308
|
+
if (!CLOUD_PROVIDERS[pId]) return;
|
|
309
|
+
|
|
310
|
+
const settings = loadPersistedSettings();
|
|
311
|
+
settings.activeProvider = pId;
|
|
312
|
+
if (!settings.providerModels) settings.providerModels = {};
|
|
313
|
+
|
|
314
|
+
if (model) {
|
|
315
|
+
const cleanModel = String(model).trim();
|
|
316
|
+
settings.providerModels[pId] = cleanModel;
|
|
317
|
+
settings.activeModel = cleanModel;
|
|
318
|
+
if (CLOUD_PROVIDERS[pId]) CLOUD_PROVIDERS[pId].defaultModel = cleanModel;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const dir = path.dirname(SETTINGS_FILE);
|
|
323
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
324
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error(`Failed to save settings to ${SETTINGS_FILE}:`, err.message);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function savePersistedProviderModel(providerId, model) {
|
|
331
|
+
const pId = String(providerId || '').toLowerCase().trim();
|
|
332
|
+
const cleanModel = String(model || '').trim();
|
|
333
|
+
if (!CLOUD_PROVIDERS[pId] || !cleanModel) return;
|
|
334
|
+
|
|
335
|
+
const settings = loadPersistedSettings();
|
|
336
|
+
if (!settings.providerModels) settings.providerModels = {};
|
|
337
|
+
settings.providerModels[pId] = cleanModel;
|
|
338
|
+
if (settings.activeProvider === pId) {
|
|
339
|
+
settings.activeModel = cleanModel;
|
|
340
|
+
}
|
|
341
|
+
if (CLOUD_PROVIDERS[pId]) CLOUD_PROVIDERS[pId].defaultModel = cleanModel;
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const dir = path.dirname(SETTINGS_FILE);
|
|
345
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
346
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error(`Failed to save model to ${SETTINGS_FILE}:`, err.message);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function resolveDefaultProvider() {
|
|
353
|
+
const explicit = String(process.env.LLM_PROVIDER || '').toLowerCase().trim();
|
|
354
|
+
if (explicit && CLOUD_PROVIDERS[explicit]) return explicit;
|
|
355
|
+
|
|
356
|
+
const persisted = getPersistedActiveProvider();
|
|
357
|
+
if (persisted && CLOUD_PROVIDERS[persisted]) {
|
|
358
|
+
const def = CLOUD_PROVIDERS[persisted];
|
|
359
|
+
if (def.isLocal || def.apiKey) {
|
|
360
|
+
return persisted;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (process.env.OPENROUTER_API_KEY && !process.env.OPENROUTER_API_KEY.includes('...')) return 'openrouter';
|
|
365
|
+
if (process.env.OPENAI_API_KEY && !process.env.OPENAI_API_KEY.includes('...')) return 'openai';
|
|
366
|
+
if (process.env.GEMINI_API_KEY && !process.env.GEMINI_API_KEY.includes('...')) return 'gemini';
|
|
367
|
+
if (process.env.DEEPSEEK_API_KEY && !process.env.DEEPSEEK_API_KEY.includes('...')) return 'deepseek';
|
|
368
|
+
if (process.env.GROQ_API_KEY && !process.env.GROQ_API_KEY.includes('...')) return 'groq';
|
|
369
|
+
if (process.env.LLM_API_KEY && process.env.LLM_BASE_URL) return 'custom';
|
|
370
|
+
|
|
371
|
+
return 'lmstudio';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function getProvider(id) {
|
|
375
|
+
const pId = String(id || '').toLowerCase().trim();
|
|
376
|
+
return CLOUD_PROVIDERS[pId] || null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function getProviderList() {
|
|
380
|
+
return Object.values(CLOUD_PROVIDERS);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function getRecommendedModels(providerId) {
|
|
384
|
+
const provider = getProvider(providerId);
|
|
385
|
+
return provider ? (provider.recommendedModels || []) : [];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let persistedProviderKeys = {};
|
|
389
|
+
const providerKeyEncryptionSecret = process.env.KIKU_PROVIDER_KEY_ENCRYPTION_KEY ||
|
|
390
|
+
(process.execArgv.includes('--test') ? 'kiku-agent-test-process-only-key' : '');
|
|
391
|
+
|
|
392
|
+
export function isProviderKeyPersistenceEnabled() {
|
|
393
|
+
return Boolean(providerKeyEncryptionSecret);
|
|
260
394
|
}
|
|
261
395
|
|
|
262
|
-
|
|
263
|
-
|
|
396
|
+
function encryptProviderKeys(keys) {
|
|
397
|
+
if (!providerKeyEncryptionSecret) return null;
|
|
398
|
+
const key = crypto.createHash('sha256').update(providerKeyEncryptionSecret).digest();
|
|
399
|
+
const iv = crypto.randomBytes(12);
|
|
400
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
401
|
+
const ciphertext = Buffer.concat([cipher.update(JSON.stringify(keys), 'utf8'), cipher.final()]);
|
|
402
|
+
return {
|
|
403
|
+
version: 1,
|
|
404
|
+
algorithm: 'aes-256-gcm',
|
|
405
|
+
iv: iv.toString('base64'),
|
|
406
|
+
tag: cipher.getAuthTag().toString('base64'),
|
|
407
|
+
ciphertext: ciphertext.toString('base64'),
|
|
408
|
+
};
|
|
264
409
|
}
|
|
265
410
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
411
|
+
function decryptProviderKeys(envelope) {
|
|
412
|
+
if (!providerKeyEncryptionSecret || envelope?.version !== 1 || envelope?.algorithm !== 'aes-256-gcm') return {};
|
|
413
|
+
const key = crypto.createHash('sha256').update(providerKeyEncryptionSecret).digest();
|
|
414
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(envelope.iv, 'base64'));
|
|
415
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, 'base64'));
|
|
416
|
+
const plaintext = Buffer.concat([
|
|
417
|
+
decipher.update(Buffer.from(envelope.ciphertext, 'base64')),
|
|
418
|
+
decipher.final(),
|
|
419
|
+
]).toString('utf8');
|
|
420
|
+
const parsed = JSON.parse(plaintext);
|
|
421
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
269
422
|
}
|
|
270
423
|
|
|
271
424
|
export function loadPersistedProviderKeys() {
|
|
@@ -273,159 +426,175 @@ export function loadPersistedProviderKeys() {
|
|
|
273
426
|
if (fs.existsSync(PROVIDER_KEYS_FILE)) {
|
|
274
427
|
const data = fs.readFileSync(PROVIDER_KEYS_FILE, 'utf-8');
|
|
275
428
|
const parsed = JSON.parse(data);
|
|
276
|
-
|
|
277
|
-
|
|
429
|
+
const keys = parsed?.ciphertext
|
|
430
|
+
? decryptProviderKeys(parsed)
|
|
431
|
+
: (process.env.KIKU_ALLOW_PLAINTEXT_PROVIDER_KEYS === 'true' ? parsed : {});
|
|
432
|
+
if (keys && typeof keys === 'object') {
|
|
433
|
+
persistedProviderKeys = { ...keys };
|
|
434
|
+
for (const [pId, key] of Object.entries(keys)) {
|
|
278
435
|
const providerDef = CLOUD_PROVIDERS[pId.toLowerCase()];
|
|
279
|
-
if (providerDef && !providerDef.isLocal && !providerDef.apiKey && typeof key === 'string' && key.trim()) {
|
|
280
|
-
providerDef.apiKey = key.trim();
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
} catch {}
|
|
287
|
-
return {};
|
|
288
|
-
}
|
|
289
|
-
|
|
436
|
+
if (providerDef && !providerDef.isLocal && !providerDef.apiKey && typeof key === 'string' && key.trim()) {
|
|
437
|
+
providerDef.apiKey = key.trim();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return { ...persistedProviderKeys };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
} catch {}
|
|
444
|
+
return {};
|
|
445
|
+
}
|
|
446
|
+
|
|
290
447
|
export function savePersistedProviderKey(providerId, apiKey) {
|
|
291
|
-
const pId = String(providerId || '').toLowerCase().trim();
|
|
292
|
-
const providerDef = CLOUD_PROVIDERS[pId];
|
|
293
|
-
if (!providerDef) {
|
|
294
|
-
throw new Error(`Unknown provider "${providerId}".`);
|
|
295
|
-
}
|
|
296
|
-
const cleanKey = String(apiKey || '').trim();
|
|
297
|
-
providerDef.apiKey = cleanKey;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
} else {
|
|
309
|
-
delete existing[pId];
|
|
310
|
-
}
|
|
311
|
-
|
|
448
|
+
const pId = String(providerId || '').toLowerCase().trim();
|
|
449
|
+
const providerDef = CLOUD_PROVIDERS[pId];
|
|
450
|
+
if (!providerDef) {
|
|
451
|
+
throw new Error(`Unknown provider "${providerId}".`);
|
|
452
|
+
}
|
|
453
|
+
const cleanKey = String(apiKey || '').trim();
|
|
454
|
+
providerDef.apiKey = cleanKey;
|
|
455
|
+
|
|
456
|
+
const existing = { ...persistedProviderKeys };
|
|
457
|
+
|
|
458
|
+
if (cleanKey) {
|
|
459
|
+
existing[pId] = cleanKey;
|
|
460
|
+
} else {
|
|
461
|
+
delete existing[pId];
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
persistedProviderKeys = existing;
|
|
312
465
|
try {
|
|
466
|
+
const envelope = encryptProviderKeys(existing);
|
|
467
|
+
if (!envelope) return { provider: pId, hasKey: Boolean(cleanKey), persisted: false };
|
|
313
468
|
const dir = path.dirname(PROVIDER_KEYS_FILE);
|
|
314
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
315
|
-
fs.writeFileSync(PROVIDER_KEYS_FILE, JSON.stringify(
|
|
316
|
-
} catch (err) {
|
|
317
|
-
console.error(`Failed to save provider key to ${PROVIDER_KEYS_FILE}:`, err.message);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return { provider: pId, hasKey: Boolean(cleanKey) };
|
|
321
|
-
}
|
|
322
|
-
|
|
469
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
470
|
+
fs.writeFileSync(PROVIDER_KEYS_FILE, JSON.stringify(envelope, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
471
|
+
} catch (err) {
|
|
472
|
+
console.error(`Failed to save provider key to ${PROVIDER_KEYS_FILE}:`, err.message);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return { provider: pId, hasKey: Boolean(cleanKey) };
|
|
476
|
+
}
|
|
477
|
+
|
|
323
478
|
export function getPersistedProviderKeys() {
|
|
324
|
-
|
|
325
|
-
if (fs.existsSync(PROVIDER_KEYS_FILE)) {
|
|
326
|
-
return JSON.parse(fs.readFileSync(PROVIDER_KEYS_FILE, 'utf-8')) || {};
|
|
327
|
-
}
|
|
328
|
-
} catch {}
|
|
329
|
-
return {};
|
|
479
|
+
return { ...persistedProviderKeys };
|
|
330
480
|
}
|
|
331
|
-
|
|
332
|
-
loadPersistedSettings();
|
|
333
|
-
loadPersistedProviderKeys();
|
|
334
|
-
|
|
335
|
-
const defaultProviderId = resolveDefaultProvider();
|
|
336
|
-
const defaultProviderConfig = CLOUD_PROVIDERS[defaultProviderId] || CLOUD_PROVIDERS.lmstudio;
|
|
337
|
-
|
|
338
|
-
export const config = {
|
|
339
|
-
activeProvider: defaultProviderId,
|
|
340
|
-
providers: CLOUD_PROVIDERS,
|
|
341
|
-
|
|
342
|
-
lmStudio: {
|
|
343
|
-
baseUrl: defaultProviderConfig.baseUrl,
|
|
344
|
-
apiKey: defaultProviderConfig.apiKey,
|
|
345
|
-
model: defaultProviderConfig.defaultModel,
|
|
346
|
-
defaultModel: defaultProviderConfig.defaultModel,
|
|
347
|
-
temperature: 0.3,
|
|
348
|
-
maxTokens:
|
|
349
|
-
requestTimeoutMs: 180000,
|
|
350
|
-
},
|
|
351
|
-
|
|
352
|
-
agent: {
|
|
353
|
-
maxIterations:
|
|
354
|
-
slidingWindowSize:
|
|
355
|
-
language: 'auto',
|
|
356
|
-
systemPersona: `You are "Kiku AI" (จวีจิ้งอี AI ผู้ช่วยอัจฉริยะ / 鞠婧祎智能AI助手), an expert AI specialist on the Chinese singer, actress, and artist Ju Jingyi (鞠婧祎 - จวีจิ้งอี).
|
|
357
|
-
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).
|
|
358
|
-
|
|
359
|
-
=== CRITICAL INSTRUCTIONS ===
|
|
360
|
-
|
|
361
|
-
1.
|
|
362
|
-
-
|
|
363
|
-
-
|
|
364
|
-
-
|
|
365
|
-
|
|
366
|
-
2. DYNAMIC LANGUAGE MIRRORING:
|
|
367
|
-
- THAI INPUT ➔ Reply in polite Thai (ใช้ ค่ะ/นะคะ เสมอ)
|
|
368
|
-
- CHINESE INPUT ➔ Reply in fluent, polite Chinese (简体/繁体中文)
|
|
369
|
-
- ENGLISH INPUT ➔ Reply in polished, professional English
|
|
370
|
-
- MIXED INPUT ➔ Match dominant language while preserving bilingual titles
|
|
371
|
-
|
|
372
|
-
3. RESPONSE FORMATTING (CRITICAL — NO SYSTEM LEAK):
|
|
373
|
-
- NEVER include "Thinking Process", "Tool Calling", "Action:", "Action Input:", or any internal reasoning steps in your response text.
|
|
374
|
-
- NEVER include tool call JSON, function names, or parameter schemas in your response.
|
|
375
|
-
- NEVER include advertisements, sponsor mentions, or promotional content in your response.
|
|
376
|
-
- Emoji are optional and must be semantic: use at most one relevant emoji per response, never decorate every heading or bullet.
|
|
377
|
-
- Your response must be CLEAN user-facing content ONLY — formatted with Markdown headers (###), bold (**text**), bullet points (•), and clickable links.
|
|
378
|
-
- Think silently using tools, then present ONLY the final answer to the user.
|
|
379
|
-
|
|
380
|
-
4. REAL YOUTUBE & MEDIA LINKS (WHEN DISCUSSING MEDIA):
|
|
381
|
-
- Provide REAL, WORKING, CLICKABLE links in Markdown format:
|
|
382
|
-
• Direct Watch Link: [Watch on YouTube](https://www.youtube.com/watch?v=VIDEO_ID)
|
|
383
|
-
• Search Link: https://www.youtube.com/results?search_query=QUERY
|
|
384
|
-
- NEVER use fake placeholder links like "watch?v=xxx".
|
|
385
|
-
|
|
386
|
-
5. SMART TOOL SELECTION (
|
|
387
|
-
-
|
|
388
|
-
-
|
|
389
|
-
-
|
|
390
|
-
-
|
|
391
|
-
-
|
|
392
|
-
-
|
|
393
|
-
-
|
|
394
|
-
-
|
|
395
|
-
-
|
|
396
|
-
-
|
|
397
|
-
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
• "
|
|
407
|
-
•
|
|
408
|
-
•
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
-
|
|
416
|
-
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
-
|
|
422
|
-
- Do not
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
481
|
+
|
|
482
|
+
loadPersistedSettings();
|
|
483
|
+
loadPersistedProviderKeys();
|
|
484
|
+
|
|
485
|
+
const defaultProviderId = resolveDefaultProvider();
|
|
486
|
+
const defaultProviderConfig = CLOUD_PROVIDERS[defaultProviderId] || CLOUD_PROVIDERS.lmstudio;
|
|
487
|
+
|
|
488
|
+
export const config = {
|
|
489
|
+
activeProvider: defaultProviderId,
|
|
490
|
+
providers: CLOUD_PROVIDERS,
|
|
491
|
+
|
|
492
|
+
lmStudio: {
|
|
493
|
+
baseUrl: defaultProviderConfig.baseUrl,
|
|
494
|
+
apiKey: defaultProviderConfig.apiKey,
|
|
495
|
+
model: defaultProviderConfig.defaultModel,
|
|
496
|
+
defaultModel: defaultProviderConfig.defaultModel,
|
|
497
|
+
temperature: 0.3,
|
|
498
|
+
maxTokens: 8192,
|
|
499
|
+
requestTimeoutMs: 180000,
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
agent: {
|
|
503
|
+
maxIterations: 6,
|
|
504
|
+
slidingWindowSize: 12,
|
|
505
|
+
language: 'auto',
|
|
506
|
+
systemPersona: `You are "Kiku AI" (จวีจิ้งอี AI ผู้ช่วยอัจฉริยะ / 鞠婧祎智能AI助手), an expert AI specialist and ultimate comprehensive encyclopedia on the Chinese singer, actress, and artist Ju Jingyi (鞠婧祎 - จวีจิ้งอี).
|
|
507
|
+
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).
|
|
508
|
+
|
|
509
|
+
=== CRITICAL INSTRUCTIONS ===
|
|
510
|
+
|
|
511
|
+
1. THOROUGH, COMPREHENSIVE & RICH FAN RESPONSES:
|
|
512
|
+
- Provide rich, in-depth, and comprehensive responses for fans with full context, storylines, character analysis, trivia, discography details, behind-the-scenes facts, and career history.
|
|
513
|
+
- Do NOT artificially truncate, summarize away key details, or cut answers short — provide full, richly structured answers with markdown headers, tables, bullet points, and working media links.
|
|
514
|
+
- For simple greetings ("Hi", "Hello", "สวัสดี", "你好"): Reply warmly and politely in 1-2 sentences.
|
|
515
|
+
|
|
516
|
+
2. DYNAMIC LANGUAGE MIRRORING:
|
|
517
|
+
- THAI INPUT ➔ Reply in polite Thai (ใช้ ค่ะ/นะคะ เสมอ)
|
|
518
|
+
- CHINESE INPUT ➔ Reply in fluent, polite Chinese (简体/繁体中文)
|
|
519
|
+
- ENGLISH INPUT ➔ Reply in polished, professional English
|
|
520
|
+
- MIXED INPUT ➔ Match dominant language while preserving bilingual titles
|
|
521
|
+
|
|
522
|
+
3. RESPONSE FORMATTING (CRITICAL — NO SYSTEM LEAK):
|
|
523
|
+
- NEVER include "Thinking Process", "Tool Calling", "Action:", "Action Input:", or any internal reasoning steps in your response text.
|
|
524
|
+
- NEVER include tool call JSON, function names, or parameter schemas in your response.
|
|
525
|
+
- NEVER include advertisements, sponsor mentions, or promotional content in your response.
|
|
526
|
+
- Emoji are optional and must be semantic: use at most one relevant emoji per response, never decorate every heading or bullet.
|
|
527
|
+
- Your response must be CLEAN user-facing content ONLY — formatted with Markdown headers (###), bold (**text**), bullet points (•), and clickable links.
|
|
528
|
+
- Think silently using tools, then present ONLY the final answer to the user.
|
|
529
|
+
|
|
530
|
+
4. REAL YOUTUBE & MEDIA LINKS (WHEN DISCUSSING MEDIA):
|
|
531
|
+
- Provide REAL, WORKING, CLICKABLE links in Markdown format:
|
|
532
|
+
• Direct Watch Link: [Watch on YouTube](https://www.youtube.com/watch?v=VIDEO_ID)
|
|
533
|
+
• Search Link: https://www.youtube.com/results?search_query=QUERY
|
|
534
|
+
- NEVER use fake placeholder links like "watch?v=xxx".
|
|
535
|
+
|
|
536
|
+
5. SMART TOOL SELECTION (MANDATORY LOCAL DATABASE FIRST):
|
|
537
|
+
- ALWAYS prioritize LOCAL in-process verified tools FIRST. They query the rich, local catalog database directly and are the primary source of truth.
|
|
538
|
+
- Music videos, stages & audio ➔ search_discography, get_music_videos, get_media_links, play_music
|
|
539
|
+
- Songs, OSTs, Singles ➔ search_discography
|
|
540
|
+
- Drama works, roles & trailers ➔ search_works, get_work_detail, get_character_roles
|
|
541
|
+
- Profile & fandom details ➔ get_profile_and_fandom
|
|
542
|
+
- Brand endorsements & ambassadorships ➔ get_brand_endorsements
|
|
543
|
+
- Awards & honors ➔ get_awards
|
|
544
|
+
- Career milestones & timeline ➔ get_career_timeline
|
|
545
|
+
- Local files & stats ➔ read_local_file, list_directory, get_system_stats
|
|
546
|
+
- MCP external tools (mcp_*) ➔ Secondary only; use when explicitly requested by user or when specialized external tools are needed.
|
|
547
|
+
- Breaking news & external facts (when local tools return insufficient/not_found data) ➔ ALWAYS fall back to web_search or web_extract.
|
|
548
|
+
|
|
549
|
+
6. FACT RETRIEVAL & CHRONOLOGICAL ACCURACY:
|
|
550
|
+
- THE CURRENT YEAR IS 2026.
|
|
551
|
+
- Always use tools to verify facts before answering. Do NOT hallucinate dates, titles, or links.
|
|
552
|
+
- Sort chronologically (newest first). Never repeat the same statement.
|
|
553
|
+
- Ju Jingyi's key 2026 projects:
|
|
554
|
+
• "Veil of Shadows (月鳞绮纪)" — Role: Lu Wuyi (露芜衣) on Youku
|
|
555
|
+
• "The Eternal Fragrance (千香)" — Role: Jiang Lifei (姜黎非) with Song Weilong on Youku
|
|
556
|
+
• "Wanhua Sin (万华世界)" — Promo OST: "Set Fire to the Silent World (着火的沉默世界)"
|
|
557
|
+
• 2026 Solo Single: "FIND THE WAY" (YouTube: 9AeErG03SZw)
|
|
558
|
+
• 2026 Special Live Stage: "Yu Ai / 雨爱" (YouTube: OMLpNyp8T7Q)
|
|
559
|
+
- 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)".
|
|
560
|
+
|
|
561
|
+
7. COMPREHENSIVE KNOWLEDGE — LEGAL & CAREER DETAILS:
|
|
562
|
+
When discussing Ju Jingyi's career or controversies, be accurate and specific:
|
|
563
|
+
- 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).
|
|
564
|
+
- 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.
|
|
565
|
+
- 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.
|
|
566
|
+
- When asked about legal issues, use web_search or web_extract to get the latest verified information.
|
|
567
|
+
|
|
568
|
+
8. RICH, IN-DEPTH & COMPREHENSIVE FAN ENCYCLOPEDIA QUALITY:
|
|
569
|
+
- Give complete, detailed explanations covering background stories, character development, musical composition, lyrics significance, and milestone achievements.
|
|
570
|
+
- Use structured Markdown formatting with tables, bullet lists, and bold headings to make deep information easy to digest for fans.
|
|
571
|
+
- Separate VERIFIED FACTS from INTERPRETATION. Plot, cast, role, platform, award, and rating claims must come from tool output. Label acting observations as analysis unless a cited review supports them.
|
|
572
|
+
- Do not turn chronology into a value judgment. A newer role is not automatically deeper, more realistic, or better acted than an older role.
|
|
573
|
+
- Avoid unsupported superlatives and career claims such as "top-tier", "unrivalled", "completely transcended idol status", or "won many awards".
|
|
574
|
+
- A catalog score without a named source and scoring scale is editorial metadata, not evidence of quality; omit it from analytical conclusions.
|
|
575
|
+
- For In Blossom (花间令), describe the premise precisely: Ju Jingyi portrays Yang Caiwei's identity living in Shangguan Zhi's appearance after the face-swap premise. Do not simplify this into two unrelated roles performed throughout the series.
|
|
576
|
+
- For The Legend of White Snake (新白娘子传奇), Bai Suzhen is the white-snake spirit who takes human form and Xu Xian is the human man. Never reverse their species or genders.
|
|
577
|
+
- For follow-ups beginning with "ดำเนินการต่อ" / "continue", answer the newly requested angle and do not reproduce the previous answer.
|
|
578
|
+
|
|
579
|
+
9. PROACTIVE MUSIC INVITATION (RELEVANCE GUARD):
|
|
580
|
+
- ONLY invite the user to listen to music or open the Mini Player when the conversation is SPECIFICALLY and DIRECTLY about music, songs, singing, or OST playback.
|
|
581
|
+
- Example: "เพลง FIND THE WAY จังหวะทรงพลังมากเลยค่ะ! อยากให้ Kiku เปิดให้ฟังมั้ยคะ?"
|
|
582
|
+
- NEVER offer to play music or ask to open the Mini Player when answering questions about drama plots, rumors, casting news, actors, awards, or general topics.
|
|
583
|
+
- If user agrees → call play_music. If user says "Stop" → call stop_music.
|
|
584
|
+
|
|
585
|
+
10. ENTITY DISAMBIGUATION & RUMOR VERIFICATION (CRITICAL GUARDRAIL):
|
|
586
|
+
- STRICT DISAMBIGUATION (NO PHONETIC CONFUSION):
|
|
587
|
+
• NEVER confuse phonetically similar pinyin or names.
|
|
588
|
+
• Specifically: 《七星彩》 (Qī Xīng Cǎi / Qi Xiang Cai / ชีซิงไฉ่) is an upcoming/rumored S+ costume romance drama from Youku x Stellar Media (恒星引力), adapted from Mingyue Dang's (明月珰) novel starring characters Ji Cheng (纪澄) and Shen Che (沈彻).
|
|
589
|
+
• 《七星彩》 is COMPLETELY UNRELATED to 《千香》 (Qiān Xiāng / The Eternal Fragrance / พันกลิ่นบุปผา).
|
|
590
|
+
• NEVER claim that an unconfirmed title or rumor is "associated with" or "is found to be" an existing OST or song in Ju Jingyi's catalog just because syllables sound alike.
|
|
591
|
+
- HANDLING UNCONFIRMED DRAMAS & MELON ACCOUNTS (吃瓜 / 选角传闻):
|
|
592
|
+
• When asked why netizens are interested in an unconfirmed project like 《七星彩》, explain the actual facts:
|
|
593
|
+
1) Production & Origin: Big S+ budget by Youku and Stellar Media (the acclaimed studio behind "Love Between Fairy and Devil").
|
|
594
|
+
2) The Buzz / Why Netizens Care: High-profile Casting Rumors (选角瓜) circulating on Weibo, Douban, and Xiaohongshu discussing multiple top visual actresses (often floating Ju Jingyi, Lu Yuxiao, Song Zu'er, etc.) for the clever and enchanting female lead Ji Cheng.
|
|
595
|
+
3) Status: Still in preparation / unconfirmed by official studio or platform announcements.
|
|
596
|
+
4) Clear distinction: Remind the user that this is an unconfirmed industry project and distinct from her confirmed works like "The Eternal Fragrance (千香)" and "Veil of Shadows (月鳞绮纪)".
|
|
597
|
+
- NEVER FABRICATE FICTIONAL CONNECTIONS:
|
|
598
|
+
• If an entity is not found in the verified catalog, use web_search to find true facts or tell the user clearly that it is not in the verified official works. NEVER invent a relationship between two unrelated titles.`,
|
|
599
|
+
}
|
|
600
|
+
};
|