kiku-agent 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +459 -407
  2. package/package.json +27 -3
  3. package/server/agent/agentCore.js +635 -267
  4. package/server/agent/appPaths.js +2 -0
  5. package/server/agent/catalogEntityResolver.js +629 -0
  6. package/server/agent/citations.js +0 -21
  7. package/server/agent/clarificationAnalytics.js +1 -1
  8. package/server/agent/clarifyingQuestions.js +286 -216
  9. package/server/agent/cli.js +528 -73
  10. package/server/agent/commandFeatures.js +123 -24
  11. package/server/agent/commandRegistry.js +128 -62
  12. package/server/agent/commands.js +1410 -95
  13. package/server/agent/config.js +253 -163
  14. package/server/agent/contextCompactor.js +239 -25
  15. package/server/agent/dynamicCatalog.js +192 -0
  16. package/server/agent/envBootstrap.js +46 -0
  17. package/server/agent/envManager.js +317 -0
  18. package/server/agent/eventBus.js +3 -3
  19. package/server/agent/feedbackLearning.js +0 -8
  20. package/server/agent/gpuMetrics.js +188 -55
  21. package/server/agent/ingestionWorker.js +339 -0
  22. package/server/agent/lanDiscovery.js +369 -0
  23. package/server/agent/llmClient.js +111 -39
  24. package/server/agent/lmStudioWakeup.js +144 -0
  25. package/server/agent/mcpClient.js +2 -2
  26. package/server/agent/memory.js +635 -494
  27. package/server/agent/modelConfig.js +487 -0
  28. package/server/agent/newsIntelligence.js +219 -0
  29. package/server/agent/newsWatch.js +85 -0
  30. package/server/agent/prompts.js +146 -0
  31. package/server/agent/publicTools.js +7 -0
  32. package/server/agent/queryDetails.js +62 -1
  33. package/server/agent/research.js +314 -35
  34. package/server/agent/routingInterceptor.js +77 -0
  35. package/server/agent/rumoredProjects.js +15 -0
  36. package/server/agent/sanitizer.js +117 -17
  37. package/server/agent/subagent.js +52 -9
  38. package/server/agent/suggestionTui.js +33 -13
  39. package/server/agent/suggestions.js +29 -13
  40. package/server/agent/telemetry.js +0 -11
  41. package/server/agent/terminalImage.js +0 -1
  42. package/server/agent/textToolProtocol.js +190 -0
  43. package/server/agent/tokenTracker.js +220 -0
  44. package/server/agent/toolRouter.js +362 -216
  45. package/server/agent/tools.js +1155 -194
  46. package/server/agent/ui.js +144 -41
  47. package/server/agent/vramService.js +572 -0
  48. package/server/agent/webSearch.js +2279 -273
  49. package/server/agent/youtubeAuth.js +82 -73
  50. package/server/agent/youtubeTranscript.js +209 -0
  51. package/server/apiRouter.js +383 -179
  52. package/server/mcp/data/brands.js +5 -5
  53. package/server/mcp/data/discography.js +56 -45
  54. package/server/mcp/data/gallery.js +9 -0
  55. package/server/mcp/data/index.js +1 -0
  56. package/server/mcp/data/industryIndex.js +251 -0
  57. package/server/mcp/data/profile.js +5 -5
  58. package/server/mcp/data/timeline.js +2 -2
  59. package/server/mcp/data/works.js +2 -2
  60. package/server/mcp/handler.js +0 -2
  61. package/server/mcp/prompts.js +42 -2
  62. package/server/mcp/resources.js +5 -1
  63. package/server/mcp/security.js +25 -14
  64. package/server/mcp/server.js +92 -3
  65. package/server/mcp/tools.js +20 -11
  66. package/server/web-chat/core/connection.js +218 -194
  67. package/server/web-chat/core/conversationContext.js +1 -1
  68. package/server/web-chat/core/directCatalogEngine.js +190 -184
  69. package/server/web-chat/core/followUpEngine.js +88 -11
  70. package/server/web-chat/core/security.js +7 -4
  71. package/server/web-chat/router.js +54 -17
  72. package/server/web-chat/routes/bilibiliRoute.js +34 -0
  73. package/server/web-chat/routes/chatRoute.js +488 -397
  74. package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
  75. package/server/web-chat/routes/exportRoute.js +2 -1
  76. package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
  77. package/server/web-chat/routes/newsRoute.js +36 -6
  78. package/server/web-chat/routes/providerRoute.js +87 -0
  79. package/server/web-chat/routes/staticRoute.js +61 -13
  80. package/server/web-chat/routes/statusRoute.js +23 -2
  81. package/server/web-chat/routes/vramRoute.js +22 -0
  82. package/server/web-chat/server.js +135 -106
  83. package/server/web-chat/ui/htmlBuilder.js +8 -8
  84. package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
  85. package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
  86. package/server/web-chat/ui/scripts/client.js +10526 -8397
  87. package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
  88. package/server/web-chat/ui/styles/main.css +7471 -4246
  89. package/server/web-chat/ui/templates/body.html +305 -179
  90. package/src/data/brandsData.js +29 -6
  91. package/src/data/discographyData.js +918 -900
  92. package/src/data/galleryData.js +66 -0
  93. package/src/data/industryIndexData.js +292 -0
  94. package/src/data/musicVideoData.js +48 -5
  95. package/src/data/profileData.js +15 -1
  96. package/src/data/timelineData.js +4 -3
  97. package/src/data/worksData.js +536 -535
  98. package/src/lib/vram/estimator.js +464 -0
  99. package/src/lib/vram/modelRegistry.js +387 -0
  100. package/tokens.css +33 -2
@@ -1,216 +1,362 @@
1
- import { toolsSchema } from './tools.js';
2
-
3
- export const TOOL_CLUSTERS = Object.freeze({
4
- CATALOG: [
5
- 'global_search',
6
- 'search_works',
7
- 'get_work_detail',
8
- 'get_character_roles',
9
- 'search_discography',
10
- 'get_profile_and_fandom',
11
- 'get_awards',
12
- 'get_brand_endorsements',
13
- 'get_career_timeline',
14
- 'get_social_feed',
15
- 'get_gallery_photos',
16
- 'get_quotes',
17
- ],
18
- MEDIA: [
19
- 'play_music',
20
- 'stop_music',
21
- 'pause_music',
22
- 'resume_music',
23
- 'next_track',
24
- 'prev_track',
25
- 'add_to_queue',
26
- 'remove_from_queue',
27
- 'clear_queue',
28
- 'get_queue',
29
- 'search_youtube',
30
- 'get_current_track',
31
- 'get_music_status',
32
- 'get_music_videos',
33
- 'get_media_links',
34
- ],
35
- RESEARCH: [
36
- 'web_search',
37
- 'web_extract',
38
- 'fetch_webpage',
39
- 'fetch_api',
40
- 'fetch_json',
41
- 'screenshot_webpage',
42
- 'verify_claim',
43
- 'run_subagent',
44
- 'run_deep_research',
45
- ],
46
- SYSTEM: [
47
- 'get_system_stats',
48
- 'manage_skill',
49
- 'read_local_file',
50
- 'list_directory',
51
- ]
52
- });
53
-
54
- const DEFAULT_UNIVERSAL_TOOLS = Object.freeze([
55
- 'global_search',
56
- 'search_works',
57
- 'search_discography',
58
- 'get_profile_and_fandom',
59
- 'get_media_links',
60
- 'web_search',
61
- 'web_extract',
62
- 'run_subagent',
63
- ]);
64
-
65
- const INTENT_PATTERNS = [
66
- {
67
- type: 'MEDIA',
68
- regex: /(เพลง|ร้อง|ฟัง|เอ็มวี|mv|music|song|play|queue|track|audio|sound|listen|stage|dance|concert|ost|bilibili|qqmusic|歌曲|单曲|首发|舞台|演唱)/i,
69
- clusters: ['MEDIA'],
70
- essentialTools: ['search_discography', 'get_music_videos', 'get_media_links', 'play_music', 'get_queue', 'search_youtube', 'get_current_track', 'get_music_status']
71
- },
72
- {
73
- type: 'WORKS',
74
- regex: /(ละคร|ซีรีส์|หนัง|ภาพยนตร์|บทบาท|ตัวละคร|ตอน|ช่อง|ฉาย|drama|series|movie|film|role|character|synopsis|actor|tv|works|broadcast|weibo|影视|剧|新剧|角色|饰演)/i,
75
- clusters: ['CATALOG'],
76
- essentialTools: ['search_works', 'get_work_detail', 'get_character_roles', 'global_search', 'get_career_timeline']
77
- },
78
- {
79
- type: 'PROFILE_AWARDS_BRANDS',
80
- regex: /(ประวัติ|วันเกิด|อายุ|บ้านเกิด|แฟนคลับ|สโลแกน|แบรนด์|พรีเซนเตอร์|โฆษณา|ทูต|รางวัล|เกียรติยศ|นิตยสาร|แฟชั่น|ปก|โซเชียล|ไอจี|profile|bio|birthday|fandom|miju|蜜橘|brand|ambassador|award|honor|endorsement|quote|fashion|magazine|cover|snh48|election|hall of fame|senbatsu|win|social|instagram|weibo|douyin|wechat|account|คำพูด|代言|彩妆|品牌|荣誉|奖项|微博之夜)/i,
81
- clusters: ['CATALOG'],
82
- essentialTools: ['get_profile_and_fandom', 'get_brand_endorsements', 'get_awards', 'get_quotes', 'get_career_timeline', 'get_gallery_photos', 'get_social_feed']
83
- },
84
- {
85
- type: 'RESEARCH_VERIFY',
86
- regex: /(ข่าว|ล่าสุด|จริงไหม|ข่าวลือ|สืบ|ตรวจสอบ|สัญญา|ศาล|star48|siba|news|update|latest|recent|rumor|verify|claim|dispute|legal|lawsuit|contract|truth)/i,
87
- clusters: ['RESEARCH'],
88
- essentialTools: ['verify_claim', 'web_search', 'web_extract', 'fetch_webpage', 'run_subagent', 'run_deep_research', 'global_search']
89
- },
90
- {
91
- type: 'SYSTEM_SKILL',
92
- regex: /(skill|learn|teach|tui|system|stats|status|memory|pref|file|dir|directory|สถิติ|จำนวน|ระบบ|สอน)/i,
93
- clusters: ['SYSTEM'],
94
- essentialTools: ['manage_skill', 'get_system_stats', 'read_local_file', 'list_directory']
95
- }
96
- ];
97
-
98
- export class DynamicToolRouter {
99
- constructor(allTools = toolsSchema) {
100
- this.allTools = Array.isArray(allTools) ? allTools : toolsSchema;
101
- this.toolsByName = new Map(this.allTools.map(t => [t.function?.name, t]));
102
- }
103
-
104
- setTools(allTools) {
105
- this.allTools = Array.isArray(allTools) ? allTools : toolsSchema;
106
- this.toolsByName = new Map(this.allTools.map(t => [t.function?.name, t]));
107
- }
108
-
109
- detectIntents(prompt = '') {
110
- const text = String(prompt || '');
111
- const matched = [];
112
-
113
- for (const pattern of INTENT_PATTERNS) {
114
- if (pattern.regex.test(text)) {
115
- matched.push(pattern);
116
- }
117
- }
118
- return matched;
119
- }
120
-
121
- extractAtMentions(prompt = '') {
122
- const text = String(prompt || '');
123
- const matches = text.match(/@([a-zA-Z0-9_-]+)/g);
124
- if (!matches) return [];
125
- return matches.map(m => m.slice(1));
126
- }
127
-
128
- routeToolsForQuery(prompt = '', options = {}) {
129
- if (options.showAll || options.disableRouting) {
130
- return [...this.allTools];
131
- }
132
-
133
- const atMentions = this.extractAtMentions(prompt);
134
- const pinnedTools = [];
135
- const selectedNames = new Set();
136
-
137
- for (const mention of atMentions) {
138
- const mentionLower = mention.toLowerCase();
139
- for (const tool of this.allTools) {
140
- const name = tool.function?.name;
141
- if (!name) continue;
142
- const nameLower = name.toLowerCase();
143
- if (
144
- nameLower === mentionLower ||
145
- nameLower === `mcp_${mentionLower}` ||
146
- nameLower.endsWith(`_${mentionLower}`) ||
147
- nameLower.startsWith(`mcp_${mentionLower}_`)
148
- ) {
149
- selectedNames.add(name);
150
- if (!pinnedTools.some(t => t.function?.name === name)) {
151
- pinnedTools.push(tool);
152
- }
153
- }
154
- }
155
- }
156
-
157
- const matchedIntents = this.detectIntents(prompt);
158
-
159
- if (matchedIntents.length === 0) {
160
- for (const name of DEFAULT_UNIVERSAL_TOOLS) {
161
- selectedNames.add(name);
162
- }
163
- } else {
164
- for (const intent of matchedIntents) {
165
- for (const toolName of intent.essentialTools) {
166
- selectedNames.add(toolName);
167
- }
168
- }
169
- selectedNames.add('global_search');
170
- selectedNames.add('web_search');
171
- selectedNames.add('web_extract');
172
- }
173
-
174
- if (options.includeExtra && Array.isArray(options.includeExtra)) {
175
- for (const name of options.includeExtra) {
176
- selectedNames.add(name);
177
- }
178
- }
179
-
180
- const routed = [...pinnedTools];
181
- const seenNames = new Set(pinnedTools.map(t => t.function?.name));
182
-
183
-
184
- for (const tool of this.allTools) {
185
- const name = tool.function?.name;
186
- if (name && !name.startsWith('mcp_') && !seenNames.has(name) && selectedNames.has(name)) {
187
- seenNames.add(name);
188
- routed.push(tool);
189
- }
190
- }
191
-
192
-
193
- const wantsMcpExplicitly = options.includeMcp === true || atMentions.some(m => m.toLowerCase().includes('mcp'));
194
- if (wantsMcpExplicitly || routed.length < 3) {
195
- for (const tool of this.allTools) {
196
- const name = tool.function?.name;
197
- if (name && name.startsWith('mcp_') && !seenNames.has(name) && (selectedNames.has(name) || wantsMcpExplicitly)) {
198
- seenNames.add(name);
199
- routed.push(tool);
200
- }
201
- }
202
- }
203
-
204
- if (routed.length === 0) {
205
- return this.allTools.slice(0, 8);
206
- }
207
-
208
- if (routed.length > 15 && !options.showAll) {
209
- return routed.slice(0, 15);
210
- }
211
-
212
- return routed;
213
- }
214
- }
215
-
216
- export const dynamicToolRouter = new DynamicToolRouter();
1
+ import { toolsSchema } from './tools.js';
2
+ import { resolveQueryEntities, detectPlatforms, detectPromotionIntent } from './catalogEntityResolver.js';
3
+
4
+ export const TOOL_CLUSTERS = Object.freeze({
5
+ CATALOG: [
6
+ 'global_search',
7
+ 'search_works',
8
+ 'get_work_detail',
9
+ 'get_character_roles',
10
+ 'search_discography',
11
+ 'get_profile_and_fandom',
12
+ 'get_awards',
13
+ 'get_brand_endorsements',
14
+ 'get_career_timeline',
15
+ 'get_social_feed',
16
+ 'get_gallery_photos',
17
+ 'get_quotes',
18
+ 'record_dynamic_event',
19
+ ],
20
+ MEDIA: [
21
+ 'play_music',
22
+ 'stop_music',
23
+ 'pause_music',
24
+ 'resume_music',
25
+ 'next_track',
26
+ 'prev_track',
27
+ 'add_to_queue',
28
+ 'remove_from_queue',
29
+ 'clear_queue',
30
+ 'get_queue',
31
+ 'search_youtube',
32
+ 'get_current_track',
33
+ 'get_music_status',
34
+ 'get_music_videos',
35
+ 'get_media_links',
36
+ ],
37
+ RESEARCH: [
38
+ 'web_search',
39
+ 'web_extract',
40
+ 'fetch_webpage',
41
+ 'fetch_api',
42
+ 'fetch_json',
43
+ 'screenshot_webpage',
44
+ 'verify_claim',
45
+ 'run_subagent',
46
+ 'run_deep_research',
47
+ 'search_news',
48
+ 'fetch_rss',
49
+ 'search_encyclopedia',
50
+ 'search_music_platforms',
51
+ 'search_chinese_web',
52
+ 'search_bilibili',
53
+ 'search_web_images',
54
+ ],
55
+ SYSTEM: [
56
+ 'get_system_stats',
57
+ 'manage_skill',
58
+ 'read_local_file',
59
+ 'list_directory',
60
+ 'checkpoint_step',
61
+ 'record_failure',
62
+ 'load_active_run',
63
+ ]
64
+ });
65
+
66
+ const DEFAULT_UNIVERSAL_TOOLS = Object.freeze([
67
+ 'global_search',
68
+ 'get_profile_and_fandom',
69
+ 'search_works',
70
+ 'search_discography',
71
+ 'get_character_roles',
72
+ 'get_awards',
73
+ 'get_quotes',
74
+ 'get_brand_endorsements',
75
+ 'web_search',
76
+ 'web_extract',
77
+ ]);
78
+
79
+ export const TOOL_DEPENDENCY_BUNDLES = Object.freeze({
80
+ search_works: ['get_gallery_photos'],
81
+ get_work_detail: ['get_gallery_photos'],
82
+ search_discography: ['get_music_videos'],
83
+ get_brand_endorsements: ['get_gallery_photos'],
84
+ get_awards: ['get_career_timeline'],
85
+ });
86
+
87
+ export function expandActiveToolsWithDependencies(selectedTools, allToolsSchema) {
88
+ if (!Array.isArray(selectedTools)) return [];
89
+ const tools = [...selectedTools];
90
+ const toolSet = new Set(tools.map(t => t.function?.name || t.name));
91
+ const schemaMap = new Map((allToolsSchema || []).map(t => [t.function?.name || t.name, t]));
92
+
93
+ for (const name of Array.from(toolSet)) {
94
+ const dependencies = TOOL_DEPENDENCY_BUNDLES[name] || [];
95
+ for (const dep of dependencies) {
96
+ if (!toolSet.has(dep) && schemaMap.has(dep)) {
97
+ tools.push(schemaMap.get(dep));
98
+ toolSet.add(dep);
99
+ }
100
+ }
101
+ }
102
+ return tools;
103
+ }
104
+
105
+ const INTENT_PATTERNS = [
106
+ {
107
+ type: 'MEDIA',
108
+ regex: /(เพลง|ร้อง|ฟัง|เอ็มวี|\bmv\b|\bmusics?\b|\bsongs?\b|\bsingles?\b|\bsolos?\b|\balbums?\b|\bep\b|\bdiscography\b|\bdiscs?\b|\btracks?\b|\btunes?\b|\bmelod(?:y|ies)\b|\bplay\b|\bqueues?\b|\baudios?\b|\bsounds?\b|\blisten\b|\bstages?\b|\bdances?\b|\bdancing\b|\bconcerts?\b|\bost\b|\bbilibili\b|b站|\bqqmusic\b|\bspotify\b|(?<!เครื่อง)ดนตรี|อัลบั้ม|ซิงเกิล|โซโล่|歌曲|单曲|首发|舞台|演唱|คลิป|วิดีโอ|\bvideos?\b)/i,
109
+ clusters: ['MEDIA'],
110
+ essentialTools: ['search_discography', 'get_music_videos', 'get_media_links', 'play_music', 'get_queue', 'search_youtube', 'get_current_track', 'get_music_status', 'search_bilibili', 'search_music_platforms', 'get_gallery_photos']
111
+ },
112
+ {
113
+ type: 'WORKS',
114
+ regex: /(ละคร|ซีรีส์|หนัง|ภาพยนตร์|บทบาท|ตัวละคร|ตอน|ช่อง|ฉาย|\bdramas?\b|\bseries\b|\bmovies?\b|\bfilms?\b|\broles?\b|\bcharacters?\b|\bsynops(?:is|es)\b|\bactors?\b|\btv\b|\bworks?\b|\bbroadcast(?:s|ing)?\b|\bweibo\b|影视|剧|新剧|角色|饰演)/i,
115
+ clusters: ['CATALOG'],
116
+ essentialTools: ['search_works', 'get_work_detail', 'get_character_roles', 'global_search', 'get_career_timeline', 'get_gallery_photos']
117
+ },
118
+ {
119
+ type: 'TIMELINE_ERA',
120
+ regex: /(ปี\s*\d{4}|ค\.ศ\.\s*\d{4}|พ\.ศ\.\s*\d{4}|\b20\d{2}\b|ไทม์ไลน์|ประวัติการทำงาน|เส้นทางอาชีพ|เดบิวต์|เหตุการณ์|ลำดับเวลา|การเปลี่ยนแปลง|วาไรตี้|ช่วงปี|\byears?\b|\btimelines?\b|\bmilestones?\b|\beras?\b|\bdebut\b|\bevents?\b|\bvariety\b|\bchronolog(?:y|ical)\b|\bhistory\b|年代|时期|出道|发展历程|大事件|演艺经历|时间线)/i,
121
+ clusters: ['CATALOG'],
122
+ essentialTools: ['get_career_timeline', 'get_profile_and_fandom', 'get_awards', 'search_works']
123
+ },
124
+ {
125
+ type: 'PROFILE_AWARDS_BRANDS',
126
+ regex: /(ประวัติ|วันเกิด|เกิด(?!ขึ้น)|อายุ|บ้านเกิด|เมือง|มณฑล|แฟนคลับ|สโลแกน|แบรนด์|พรีเซนเตอร์|พรีเซ็นเตอร์|โฆษณา|แคมเปญ|โปรโมท|โปรโมต|การโปรโมท|การโปรโมต|ทูต|รางวัล|เกียรติยศ|นิตยสาร|แฟชั่น|ปก|โซเชียล|ไอจี|เรียน|การศึกษา|เครื่องดนตรี|ไวโอลิน|ส่วนสูง|สูง(?!สุด)|เลือด|กรุ๊ปเลือด|แมว|คติพจน์|คำขวัญ|สตูดิโอ|\bprofile\b|\bbio(?:graphy)?\b|\bbirthdays?\b|\bbirth\b|\bborn\b|\bbirthplace\b|\bhometown\b|\bcities?\b|\bcity\b|\bprovince\b|\bfandom\b|\bmiju\b|蜜橘|\bbrands?\b|\bambassadors?\b|\bawards?\b|\bhonou?rs?\b|\bendorsements?\b|\bquotes?\b|\bmotto\b|\bfashion\b|\bmagazines?\b|\bcovers?\b|\bcommercial\b|\bcf\b|\bpromos?\b|\bpromotions?\b|\bpromoted\b|\bcampaigns?\b|\bsponsor(?:s|ed|ship)?\b|\bpartnerships?\b|\bsnh48\b|\belections?\b|\bhall of fame\b|\bsenbatsu\b|\bwins?\b|\bsocial\b|\binstagram\b|\bweibo\b|\bdouyin\b|\bwechat\b|\baccounts?\b|\beducation\b|\bschools?\b|\bcolleges?\b|\buniversity\b|\binstruments?\b|\bviolin\b|\bheight\b|\bblood\b|\bcats?\b|\bstudios?\b|\bindependent\b|คำพูด|出生|籍贯|家乡|城市|省份|身高|血型|学历|毕业|乐器|小提琴|猫|座右铭|格言|代言|彩妆|品牌|荣誉|奖项|微博之夜|推广|商务|地广|物料)/i,
127
+ clusters: ['CATALOG'],
128
+ essentialTools: ['get_profile_and_fandom', 'get_brand_endorsements', 'get_awards', 'get_quotes', 'get_career_timeline', 'get_gallery_photos', 'get_social_feed']
129
+ },
130
+ {
131
+ type: 'RESEARCH_VERIFY',
132
+ regex: /(ข่าว|ล่าสุด|จริงไหม|ข่าวลือ|สืบ|ตรวจสอบ|สัญญา|ศาล|\bstar48\b|\bsiba\b|\bnews\b|\bupdates?\b|\blatest\b|\brecent(?:ly)?\b|\brumors?\b|\bverify\b|\bclaims?\b|\bdisputes?\b|\blegal\b|\blawsuits?\b|\bcontracts?\b|\btruth\b|新闻|最新消息|动态|热搜|资讯|爆料|近况|百科|百度百科|维基|\bencyclopedia\b|\bbaike\b|\bwikipedia?\b)/i,
133
+ clusters: ['RESEARCH'],
134
+ essentialTools: ['verify_claim', 'web_search', 'web_extract', 'fetch_webpage', 'run_subagent', 'run_deep_research', 'global_search', 'search_news', 'search_encyclopedia', 'search_chinese_web', 'get_gallery_photos']
135
+ },
136
+ {
137
+ type: 'SYSTEM_SKILL',
138
+ regex: /(\bskills?\b|\blearn(?:ing|ed)?\b|\bteach(?:ing|er)?\b|\btui\b|\bsystems?\b|\bstats\b|\bstatus\b|\bmemory\b|\bprefs?\b|\bfiles?\b|\bdirs?\b|\bdirector(?:y|ies)\b|สถิติ|จำนวน|ระบบ|สอน)/i,
139
+ clusters: ['SYSTEM'],
140
+ essentialTools: ['manage_skill', 'get_system_stats', 'read_local_file', 'list_directory']
141
+ }
142
+ ];
143
+
144
+ const MAX_ROUTED_TOOLS = 15;
145
+
146
+ export class DynamicToolRouter {
147
+ constructor(allTools = toolsSchema) {
148
+ this.allTools = Array.isArray(allTools) ? allTools : toolsSchema;
149
+ this.toolsByName = new Map(this.allTools.map(t => [t.function?.name, t]));
150
+ }
151
+
152
+ setTools(allTools) {
153
+ this.allTools = Array.isArray(allTools) ? allTools : toolsSchema;
154
+ this.toolsByName = new Map(this.allTools.map(t => [t.function?.name, t]));
155
+ }
156
+
157
+ detectIntents(prompt = '') {
158
+ const text = String(prompt || '');
159
+ const matched = [];
160
+ const matchedTypes = new Set();
161
+
162
+
163
+ for (const pattern of INTENT_PATTERNS) {
164
+ if (pattern.regex.test(text)) {
165
+ matched.push(pattern);
166
+ matchedTypes.add(pattern.type);
167
+ }
168
+ }
169
+
170
+
171
+ const resolution = resolveQueryEntities(text);
172
+
173
+ if ((resolution.hasBrand || resolution.isPromotion) && !matchedTypes.has('PROFILE_AWARDS_BRANDS')) {
174
+ const brandPattern = INTENT_PATTERNS.find(p => p.type === 'PROFILE_AWARDS_BRANDS');
175
+ if (brandPattern) {
176
+ matched.push(brandPattern);
177
+ matchedTypes.add('PROFILE_AWARDS_BRANDS');
178
+ }
179
+ }
180
+
181
+ if (resolution.hasWork && !matchedTypes.has('WORKS')) {
182
+ const workPattern = INTENT_PATTERNS.find(p => p.type === 'WORKS');
183
+ if (workPattern) {
184
+ matched.push(workPattern);
185
+ matchedTypes.add('WORKS');
186
+ }
187
+ }
188
+
189
+ if (resolution.hasSong && !matchedTypes.has('MEDIA')) {
190
+ const mediaPattern = INTENT_PATTERNS.find(p => p.type === 'MEDIA');
191
+ if (mediaPattern) {
192
+ matched.push(mediaPattern);
193
+ matchedTypes.add('MEDIA');
194
+ }
195
+ }
196
+
197
+ if (resolution.platforms.length > 0 && !matchedTypes.has('MEDIA')) {
198
+ const mediaPattern = INTENT_PATTERNS.find(p => p.type === 'MEDIA');
199
+ if (mediaPattern) {
200
+ matched.push(mediaPattern);
201
+ matchedTypes.add('MEDIA');
202
+ }
203
+ }
204
+
205
+ return matched;
206
+ }
207
+
208
+ extractAtMentions(prompt = '') {
209
+ const text = String(prompt || '');
210
+ const matches = text.match(/@([a-zA-Z0-9_-]+)/g);
211
+ if (!matches) return [];
212
+ return matches.map(m => m.slice(1));
213
+ }
214
+
215
+ routeToolsForQuery(prompt = '', options = {}) {
216
+ if (options.showAll || options.disableRouting) {
217
+ return [...this.allTools];
218
+ }
219
+
220
+ const atMentions = this.extractAtMentions(prompt);
221
+ const pinnedTools = [];
222
+ const selectedNames = new Set();
223
+ const highPriorityTools = new Set();
224
+
225
+ for (const mention of atMentions) {
226
+ const mentionLower = mention.toLowerCase();
227
+ for (const tool of this.allTools) {
228
+ const name = tool.function?.name;
229
+ if (!name) continue;
230
+ const nameLower = name.toLowerCase();
231
+ if (
232
+ nameLower === mentionLower ||
233
+ nameLower === `mcp_${mentionLower}` ||
234
+ nameLower.endsWith(`_${mentionLower}`) ||
235
+ nameLower.startsWith(`mcp_${mentionLower}_`)
236
+ ) {
237
+ selectedNames.add(name);
238
+ highPriorityTools.add(name);
239
+ if (!pinnedTools.some(t => t.function?.name === name)) {
240
+ pinnedTools.push(tool);
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ const matchedIntents = this.detectIntents(prompt);
247
+ const resolution = resolveQueryEntities(prompt);
248
+
249
+
250
+ for (const entity of resolution.matchedEntities) {
251
+ for (const toolName of (entity.recommendedTools || [])) {
252
+ selectedNames.add(toolName);
253
+ highPriorityTools.add(toolName);
254
+ }
255
+ }
256
+
257
+
258
+ if (resolution.hasBrand || resolution.isPromotion) {
259
+ selectedNames.add('get_brand_endorsements');
260
+ highPriorityTools.add('get_brand_endorsements');
261
+ }
262
+
263
+ if (resolution.platforms.includes('bilibili')) {
264
+ selectedNames.add('search_bilibili');
265
+ selectedNames.add('get_media_links');
266
+ highPriorityTools.add('search_bilibili');
267
+ }
268
+
269
+ if (resolution.platforms.includes('youtube')) {
270
+ selectedNames.add('search_youtube');
271
+ selectedNames.add('get_media_links');
272
+ highPriorityTools.add('search_youtube');
273
+ }
274
+
275
+ if (matchedIntents.length === 0) {
276
+ for (const name of DEFAULT_UNIVERSAL_TOOLS) {
277
+ selectedNames.add(name);
278
+ }
279
+ } else {
280
+ for (const intent of matchedIntents) {
281
+ for (const toolName of intent.essentialTools) {
282
+ selectedNames.add(toolName);
283
+ }
284
+ }
285
+ selectedNames.add('global_search');
286
+ selectedNames.add('web_search');
287
+ selectedNames.add('web_extract');
288
+ }
289
+
290
+ if (options.includeExtra && Array.isArray(options.includeExtra)) {
291
+ for (const name of options.includeExtra) {
292
+ selectedNames.add(name);
293
+ }
294
+ }
295
+
296
+ const routed = [...pinnedTools];
297
+ const seenNames = new Set(pinnedTools.map(t => t.function?.name));
298
+
299
+ const addToolByName = (name) => {
300
+ if (!name || name.startsWith('mcp_') || seenNames.has(name)) return;
301
+ const tool = this.toolsByName.get(name);
302
+ if (!tool) return;
303
+ seenNames.add(name);
304
+ routed.push(tool);
305
+ };
306
+
307
+ for (const tool of this.allTools) {
308
+ const name = tool.function?.name;
309
+ if (name && !name.startsWith('mcp_') && !seenNames.has(name) && highPriorityTools.has(name)) {
310
+ seenNames.add(name);
311
+ routed.push(tool);
312
+ }
313
+ }
314
+
315
+ for (const intent of matchedIntents) {
316
+ for (const toolName of intent.essentialTools) {
317
+ addToolByName(toolName);
318
+ }
319
+ }
320
+
321
+ if (matchedIntents.length > 0) {
322
+ for (const name of ['global_search', 'web_search', 'web_extract']) {
323
+ addToolByName(name);
324
+ }
325
+ }
326
+
327
+ for (const tool of this.allTools) {
328
+ const name = tool.function?.name;
329
+ if (name && !name.startsWith('mcp_') && !seenNames.has(name) && selectedNames.has(name)) {
330
+ seenNames.add(name);
331
+ routed.push(tool);
332
+ }
333
+ }
334
+
335
+ const wantsMcpExplicitly = options.includeMcp === true || atMentions.some(m => m.toLowerCase().includes('mcp'));
336
+ if (wantsMcpExplicitly || routed.length < 3) {
337
+ for (const tool of this.allTools) {
338
+ const name = tool.function?.name;
339
+ if (name && name.startsWith('mcp_') && !seenNames.has(name) && (selectedNames.has(name) || wantsMcpExplicitly)) {
340
+ seenNames.add(name);
341
+ routed.push(tool);
342
+ }
343
+ }
344
+ }
345
+
346
+ const bundled = matchedIntents.length > 0
347
+ ? expandActiveToolsWithDependencies(routed, this.allTools)
348
+ : routed;
349
+
350
+ if (bundled.length === 0) {
351
+ return this.allTools.slice(0, 8);
352
+ }
353
+
354
+ if (bundled.length > MAX_ROUTED_TOOLS && !options.showAll) {
355
+ return bundled.slice(0, MAX_ROUTED_TOOLS);
356
+ }
357
+
358
+ return bundled;
359
+ }
360
+ }
361
+
362
+ export const dynamicToolRouter = new DynamicToolRouter();