kiku-agent 1.0.4 → 1.0.6

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.
@@ -34,7 +34,66 @@ export function buildYoukuSearchUrl(query) {
34
34
  return `https://so.youku.com/search_video/q_${encodeURIComponent(query)}`;
35
35
  }
36
36
 
37
- function getCleanDiscography() {
37
+ export class ToolCache {
38
+ constructor(maxSize = 300, defaultTtlMs = 600_000) {
39
+ this.maxSize = maxSize;
40
+ this.defaultTtlMs = defaultTtlMs;
41
+ this.cache = new Map();
42
+ }
43
+
44
+ get(key) {
45
+ const entry = this.cache.get(key);
46
+ if (!entry) return null;
47
+ if (Date.now() > entry.expiresAt) {
48
+ this.cache.delete(key);
49
+ return null;
50
+ }
51
+ this.cache.delete(key);
52
+ this.cache.set(key, entry);
53
+ return entry.value;
54
+ }
55
+
56
+ set(key, value, ttlMs = this.defaultTtlMs) {
57
+ if (this.cache.size >= this.maxSize) {
58
+ const oldestKey = this.cache.keys().next().value;
59
+ if (oldestKey) this.cache.delete(oldestKey);
60
+ }
61
+ this.cache.set(key, {
62
+ value,
63
+ expiresAt: Date.now() + ttlMs,
64
+ });
65
+ }
66
+
67
+ clear() {
68
+ this.cache.clear();
69
+ }
70
+ }
71
+
72
+ export const globalToolCache = new ToolCache();
73
+
74
+ const STATIC_CATALOG_TOOLS = new Set([
75
+ 'get_profile_and_fandom',
76
+ 'get_quotes',
77
+ 'get_awards',
78
+ 'get_timeline',
79
+ 'get_brand_endorsements',
80
+ 'get_gallery',
81
+ 'get_music_videos',
82
+ 'search_works',
83
+ 'search_discography',
84
+ 'get_work_detail',
85
+ 'get_social_updates',
86
+ 'get_media_links',
87
+ ]);
88
+
89
+ const WEB_CACHE_TOOLS = new Set([
90
+ 'web_search',
91
+ 'web_extract',
92
+ 'fetch_webpage',
93
+ 'fetch_json',
94
+ ]);
95
+
96
+ const CACHED_DISCOGRAPHY = (() => {
38
97
  const map = new Map();
39
98
  const allTracks = [
40
99
  ...(discographyData.latestReleases || []),
@@ -65,17 +124,25 @@ function getCleanDiscography() {
65
124
  }
66
125
  }
67
126
 
68
- return Array.from(map.values()).sort((a, b) => (b.year || 0) - (a.year || 0));
127
+ return Object.freeze(Array.from(map.values()).sort((a, b) => (b.year || 0) - (a.year || 0)));
128
+ })();
129
+
130
+ function getCleanDiscography() {
131
+ return CACHED_DISCOGRAPHY;
69
132
  }
70
133
 
71
- function getAllMusicVideos() {
134
+ const CACHED_MUSIC_VIDEOS = (() => {
72
135
  if (Array.isArray(musicVideoData)) {
73
- return musicVideoData;
136
+ return Object.freeze(musicVideoData);
74
137
  }
75
- return [
138
+ return Object.freeze([
76
139
  ...(musicVideoData.officialMvs || []).map(m => ({ ...m, category: 'Official MV' })),
77
140
  ...(musicVideoData.stagePerformances || []).map(m => ({ ...m, category: 'Stage Performance' }))
78
- ];
141
+ ]);
142
+ })();
143
+
144
+ function getAllMusicVideos() {
145
+ return CACHED_MUSIC_VIDEOS;
79
146
  }
80
147
 
81
148
  export const toolsSchema = [
@@ -620,6 +687,20 @@ export const toolsSchema = [
620
687
  }
621
688
  }
622
689
  },
690
+ {
691
+ type: "function",
692
+ function: {
693
+ name: "suggest_topics",
694
+ description: "Generate dynamic, intriguing research questions and exploration topics about Ju Jingyi across dramas, music, fashion, milestones, and trivia.",
695
+ parameters: {
696
+ type: "object",
697
+ properties: {
698
+ topic: { type: "string", description: "Optional topic or category focus (e.g. 'drama', 'music', 'fashion', '2026')." },
699
+ limit: { type: "number", description: "Number of suggestions to generate (default: 5)." }
700
+ }
701
+ }
702
+ }
703
+ },
623
704
  {
624
705
  type: "function",
625
706
  function: {
@@ -1483,6 +1564,19 @@ export const toolHandlers = {
1483
1564
  return { status: "success", claim, answer: result.answer, sources: result.sources, reports: result.reports };
1484
1565
  },
1485
1566
 
1567
+ suggest_topics: async ({ topic, limit }, options = {}) => {
1568
+ if (options?.agent?.generateSuggestions) {
1569
+ const suggestions = await options.agent.generateSuggestions({ topic, limit, signal: options.signal });
1570
+ return { status: "success", topic, suggestions };
1571
+ }
1572
+ const { SuggestionEngine } = await import('./suggestions.js');
1573
+ const { LMStudioClient } = await import('./llmClient.js');
1574
+ const client = options?.client || new LMStudioClient();
1575
+ const engine = new SuggestionEngine({ client });
1576
+ const suggestions = await engine.generateSuggestions({ topic, limit, signal: options?.signal });
1577
+ return { status: "success", topic, suggestions };
1578
+ },
1579
+
1486
1580
  manage_skill: async ({ action, instruction, skillId }, options = {}) => {
1487
1581
  let memory = options?.agent?.memory || options?.memory;
1488
1582
  if (!memory) {
@@ -1628,7 +1722,9 @@ export function normalizeToolResult(toolName, result) {
1628
1722
  if (normalized.error && !normalized.message) normalized.message = String(normalized.error);
1629
1723
  if (normalized.status === 'success' && (normalized.count === 0 || normalized.returnedCount === 0)) {
1630
1724
  normalized.status = 'not_found';
1631
- if (!normalized.message) normalized.message = 'No matching data found.';
1725
+ if (!normalized.message) {
1726
+ normalized.message = 'No matching data found in local catalog. Hint: You can use web_search or web_extract to look up online information.';
1727
+ }
1632
1728
  }
1633
1729
  return normalized;
1634
1730
  }
@@ -1652,6 +1748,15 @@ export async function executeTool(toolName, args = {}, options = {}) {
1652
1748
  };
1653
1749
  }
1654
1750
 
1751
+ const isStatic = STATIC_CATALOG_TOOLS.has(toolName);
1752
+ const isWeb = WEB_CACHE_TOOLS.has(toolName);
1753
+ const cacheKey = (isStatic || isWeb) ? `${toolName}:${JSON.stringify(prepared.args)}` : null;
1754
+
1755
+ if (cacheKey && !options.noCache) {
1756
+ const cached = globalToolCache.get(cacheKey);
1757
+ if (cached) return cached;
1758
+ }
1759
+
1655
1760
  const configuredTimeout = Number(options.timeoutMs ?? process.env.KIKU_TOOL_TIMEOUT_MS ?? 45_000);
1656
1761
  const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : 45_000;
1657
1762
  let timeoutId;
@@ -1669,6 +1774,12 @@ export async function executeTool(toolName, args = {}, options = {}) {
1669
1774
  if (prepared.warnings.length > 0) {
1670
1775
  normalized.validationWarnings = prepared.warnings;
1671
1776
  }
1777
+
1778
+ if (cacheKey && normalized.status !== 'error') {
1779
+ const ttl = isStatic ? 3600_000 : 900_000;
1780
+ globalToolCache.set(cacheKey, normalized, ttl);
1781
+ }
1782
+
1672
1783
  return normalized;
1673
1784
  } catch (err) {
1674
1785
  return {