intentkit 0.8.12rc0__py3-none-any.whl → 0.8.13.dev1__py3-none-any.whl

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.

Potentially problematic release.


This version of intentkit might be problematic. Click here for more details.

Files changed (175) hide show
  1. intentkit/__init__.py +1 -1
  2. intentkit/abstracts/skill.py +2 -59
  3. intentkit/clients/twitter.py +35 -28
  4. intentkit/config/config.py +1 -0
  5. intentkit/core/agent.py +2 -279
  6. intentkit/core/asset.py +63 -16
  7. intentkit/core/engine.py +9 -5
  8. intentkit/core/scheduler.py +8 -8
  9. intentkit/models/agent.py +138 -94
  10. intentkit/models/agent_schema.json +6 -9
  11. intentkit/models/chat.py +1 -0
  12. intentkit/models/llm.csv +15 -12
  13. intentkit/skills/acolyt/__init__.py +2 -9
  14. intentkit/skills/acolyt/base.py +2 -5
  15. intentkit/skills/aixbt/__init__.py +2 -13
  16. intentkit/skills/aixbt/base.py +0 -4
  17. intentkit/skills/aixbt/projects.py +1 -2
  18. intentkit/skills/allora/__init__.py +2 -9
  19. intentkit/skills/allora/base.py +2 -5
  20. intentkit/skills/base.py +101 -27
  21. intentkit/skills/basename/__init__.py +1 -3
  22. intentkit/skills/carv/__init__.py +116 -121
  23. intentkit/skills/carv/base.py +184 -185
  24. intentkit/skills/casino/__init__.py +4 -15
  25. intentkit/skills/casino/base.py +0 -4
  26. intentkit/skills/casino/deck_draw.py +1 -2
  27. intentkit/skills/casino/deck_shuffle.py +1 -2
  28. intentkit/skills/casino/dice_roll.py +1 -2
  29. intentkit/skills/cdp/__init__.py +0 -5
  30. intentkit/skills/cdp/base.py +0 -4
  31. intentkit/skills/cdp/schema.json +1 -17
  32. intentkit/skills/chainlist/__init__.py +2 -7
  33. intentkit/skills/chainlist/base.py +0 -4
  34. intentkit/skills/common/__init__.py +2 -9
  35. intentkit/skills/common/base.py +0 -4
  36. intentkit/skills/cookiefun/__init__.py +6 -9
  37. intentkit/skills/cookiefun/base.py +0 -4
  38. intentkit/skills/cryptocompare/__init__.py +7 -24
  39. intentkit/skills/cryptocompare/base.py +0 -5
  40. intentkit/skills/cryptopanic/__init__.py +3 -6
  41. intentkit/skills/cryptopanic/base.py +53 -55
  42. intentkit/skills/cryptopanic/fetch_crypto_news.py +0 -2
  43. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +1 -3
  44. intentkit/skills/dapplooker/__init__.py +2 -9
  45. intentkit/skills/dapplooker/base.py +2 -5
  46. intentkit/skills/defillama/__init__.py +24 -74
  47. intentkit/skills/defillama/base.py +0 -4
  48. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +2 -2
  49. intentkit/skills/defillama/coins/fetch_block.py +2 -2
  50. intentkit/skills/defillama/coins/fetch_current_prices.py +2 -2
  51. intentkit/skills/defillama/coins/fetch_first_price.py +2 -2
  52. intentkit/skills/defillama/coins/fetch_historical_prices.py +2 -2
  53. intentkit/skills/defillama/coins/fetch_price_chart.py +2 -2
  54. intentkit/skills/defillama/coins/fetch_price_percentage.py +2 -2
  55. intentkit/skills/defillama/fees/fetch_fees_overview.py +2 -2
  56. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +2 -2
  57. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +2 -2
  58. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +2 -2
  59. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +2 -2
  60. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -2
  61. intentkit/skills/defillama/tvl/fetch_chains.py +2 -2
  62. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -2
  63. intentkit/skills/defillama/tvl/fetch_protocol.py +2 -2
  64. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +2 -2
  65. intentkit/skills/defillama/tvl/fetch_protocols.py +2 -2
  66. intentkit/skills/defillama/volumes/fetch_dex_overview.py +2 -2
  67. intentkit/skills/defillama/volumes/fetch_dex_summary.py +2 -2
  68. intentkit/skills/defillama/volumes/fetch_options_overview.py +2 -2
  69. intentkit/skills/defillama/yields/fetch_pool_chart.py +2 -2
  70. intentkit/skills/defillama/yields/fetch_pools.py +2 -2
  71. intentkit/skills/dexscreener/__init__.py +97 -102
  72. intentkit/skills/dexscreener/base.py +125 -130
  73. intentkit/skills/dexscreener/get_pair_info.py +2 -3
  74. intentkit/skills/dexscreener/get_token_pairs.py +2 -3
  75. intentkit/skills/dexscreener/get_tokens_info.py +2 -3
  76. intentkit/skills/dexscreener/search_token.py +2 -4
  77. intentkit/skills/dune_analytics/__init__.py +4 -6
  78. intentkit/skills/dune_analytics/base.py +50 -52
  79. intentkit/skills/dune_analytics/fetch_kol_buys.py +0 -2
  80. intentkit/skills/dune_analytics/fetch_nation_metrics.py +0 -2
  81. intentkit/skills/elfa/__init__.py +5 -18
  82. intentkit/skills/elfa/base.py +8 -10
  83. intentkit/skills/enso/__init__.py +9 -29
  84. intentkit/skills/enso/base.py +3 -6
  85. intentkit/skills/enso/route.py +1 -3
  86. intentkit/skills/erc20/__init__.py +1 -5
  87. intentkit/skills/erc721/__init__.py +1 -3
  88. intentkit/skills/firecrawl/__init__.py +5 -18
  89. intentkit/skills/firecrawl/base.py +2 -5
  90. intentkit/skills/firecrawl/crawl.py +10 -9
  91. intentkit/skills/firecrawl/query.py +3 -1
  92. intentkit/skills/firecrawl/scrape.py +8 -10
  93. intentkit/skills/firecrawl/utils.py +25 -26
  94. intentkit/skills/github/__init__.py +2 -7
  95. intentkit/skills/github/base.py +0 -4
  96. intentkit/skills/heurist/__init__.py +8 -27
  97. intentkit/skills/heurist/base.py +2 -5
  98. intentkit/skills/heurist/image_generation_animagine_xl.py +5 -5
  99. intentkit/skills/heurist/image_generation_arthemy_comics.py +5 -5
  100. intentkit/skills/heurist/image_generation_arthemy_real.py +5 -5
  101. intentkit/skills/heurist/image_generation_braindance.py +5 -5
  102. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +5 -5
  103. intentkit/skills/heurist/image_generation_flux_1_dev.py +5 -5
  104. intentkit/skills/heurist/image_generation_sdxl.py +5 -5
  105. intentkit/skills/http/__init__.py +4 -15
  106. intentkit/skills/http/base.py +0 -4
  107. intentkit/skills/lifi/__init__.py +1 -6
  108. intentkit/skills/lifi/base.py +0 -4
  109. intentkit/skills/lifi/token_execute.py +1 -4
  110. intentkit/skills/lifi/token_quote.py +1 -3
  111. intentkit/skills/moralis/__init__.py +3 -7
  112. intentkit/skills/moralis/base.py +2 -5
  113. intentkit/skills/morpho/__init__.py +1 -3
  114. intentkit/skills/nation/__init__.py +2 -7
  115. intentkit/skills/nation/base.py +4 -7
  116. intentkit/skills/openai/__init__.py +5 -18
  117. intentkit/skills/openai/base.py +8 -10
  118. intentkit/skills/openai/dalle_image_generation.py +2 -5
  119. intentkit/skills/openai/gpt_image_generation.py +2 -5
  120. intentkit/skills/openai/gpt_image_to_image.py +2 -5
  121. intentkit/skills/openai/image_to_text.py +2 -5
  122. intentkit/skills/portfolio/__init__.py +11 -35
  123. intentkit/skills/portfolio/base.py +2 -5
  124. intentkit/skills/pyth/__init__.py +1 -5
  125. intentkit/skills/slack/__init__.py +5 -17
  126. intentkit/skills/slack/base.py +0 -4
  127. intentkit/skills/supabase/__init__.py +7 -23
  128. intentkit/skills/supabase/base.py +0 -4
  129. intentkit/skills/superfluid/__init__.py +1 -3
  130. intentkit/skills/system/__init__.py +7 -24
  131. intentkit/skills/system/add_autonomous_task.py +2 -2
  132. intentkit/skills/system/delete_autonomous_task.py +2 -2
  133. intentkit/skills/system/edit_autonomous_task.py +2 -4
  134. intentkit/skills/system/list_autonomous_tasks.py +2 -2
  135. intentkit/skills/system/read_agent_api_key.py +6 -4
  136. intentkit/skills/system/regenerate_agent_api_key.py +6 -4
  137. intentkit/skills/tavily/__init__.py +3 -12
  138. intentkit/skills/tavily/base.py +2 -5
  139. intentkit/skills/tavily/tavily_extract.py +1 -2
  140. intentkit/skills/tavily/tavily_search.py +3 -3
  141. intentkit/skills/token/__init__.py +5 -10
  142. intentkit/skills/token/base.py +2 -6
  143. intentkit/skills/twitter/__init__.py +11 -35
  144. intentkit/skills/twitter/base.py +14 -16
  145. intentkit/skills/twitter/follow_user.py +0 -1
  146. intentkit/skills/twitter/get_mentions.py +0 -1
  147. intentkit/skills/twitter/get_timeline.py +0 -1
  148. intentkit/skills/twitter/get_user_by_username.py +0 -1
  149. intentkit/skills/twitter/get_user_tweets.py +0 -1
  150. intentkit/skills/twitter/like_tweet.py +0 -1
  151. intentkit/skills/twitter/post_tweet.py +2 -2
  152. intentkit/skills/twitter/reply_tweet.py +2 -2
  153. intentkit/skills/twitter/retweet.py +0 -1
  154. intentkit/skills/twitter/search_tweets.py +0 -1
  155. intentkit/skills/unrealspeech/__init__.py +2 -7
  156. intentkit/skills/unrealspeech/base.py +0 -4
  157. intentkit/skills/venice_audio/__init__.py +99 -106
  158. intentkit/skills/venice_audio/base.py +118 -121
  159. intentkit/skills/venice_audio/venice_audio.py +1 -5
  160. intentkit/skills/venice_image/__init__.py +147 -154
  161. intentkit/skills/venice_image/base.py +185 -192
  162. intentkit/skills/web_scraper/__init__.py +5 -18
  163. intentkit/skills/web_scraper/base.py +20 -4
  164. intentkit/skills/web_scraper/document_indexer.py +6 -4
  165. intentkit/skills/web_scraper/scrape_and_index.py +11 -8
  166. intentkit/skills/web_scraper/utils.py +31 -27
  167. intentkit/skills/web_scraper/website_indexer.py +7 -8
  168. intentkit/skills/weth/__init__.py +1 -5
  169. intentkit/skills/wow/__init__.py +1 -5
  170. intentkit/skills/xmtp/__init__.py +4 -15
  171. intentkit/utils/schema.py +100 -0
  172. {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev1.dist-info}/METADATA +1 -1
  173. {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev1.dist-info}/RECORD +175 -174
  174. {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev1.dist-info}/WHEEL +0 -0
  175. {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev1.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,5 @@
1
1
  from typing import TypedDict
2
2
 
3
- from intentkit.abstracts.skill import SkillStoreABC
4
3
  from intentkit.skills.aixbt.base import AIXBTBaseTool
5
4
  from intentkit.skills.aixbt.projects import AIXBTProjects
6
5
  from intentkit.skills.base import SkillConfig, SkillState
@@ -27,7 +26,6 @@ class Config(SkillConfig):
27
26
  async def get_skills(
28
27
  config: "Config",
29
28
  is_private: bool,
30
- store: SkillStoreABC,
31
29
  **_,
32
30
  ) -> list[AIXBTBaseTool]:
33
31
  """Get all AIXBT API skills."""
@@ -44,26 +42,17 @@ async def get_skills(
44
42
  available_skills.append(skill_name)
45
43
 
46
44
  # Get each skill using the cached getter
47
- return [
48
- get_aixbt_skill(
49
- name=name,
50
- store=store,
51
- )
52
- for name in available_skills
53
- ]
45
+ return [get_aixbt_skill(name) for name in available_skills]
54
46
 
55
47
 
56
48
  def get_aixbt_skill(
57
49
  name: str,
58
- store: SkillStoreABC,
59
50
  ) -> AIXBTBaseTool:
60
51
  """Get an AIXBT API skill by name."""
61
52
 
62
53
  if name == "aixbt_projects":
63
54
  if name not in _cache:
64
- _cache[name] = AIXBTProjects(
65
- skill_store=store,
66
- )
55
+ _cache[name] = AIXBTProjects()
67
56
  return _cache[name]
68
57
  else:
69
58
  raise ValueError(f"Unknown AIXBT skill: {name}")
@@ -2,7 +2,6 @@ from typing import Type
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
 
5
- from intentkit.abstracts.skill import SkillStoreABC
6
5
  from intentkit.skills.base import IntentKitSkill
7
6
 
8
7
 
@@ -12,9 +11,6 @@ class AIXBTBaseTool(IntentKitSkill):
12
11
  name: str = Field(description="The name of the tool")
13
12
  description: str = Field(description="A description of what the tool does")
14
13
  args_schema: Type[BaseModel]
15
- skill_store: SkillStoreABC = Field(
16
- description="The skill store for persisting data"
17
- )
18
14
 
19
15
  @property
20
16
  def category(self) -> str:
@@ -89,9 +89,8 @@ class AIXBTProjects(AIXBTBaseTool):
89
89
  "rate_limit_minutes"
90
90
  ):
91
91
  await self.user_rate_limit_by_category(
92
- context.user_id,
93
92
  skill_config["rate_limit_number"],
94
- skill_config["rate_limit_minutes"],
93
+ skill_config["rate_limit_minutes"] * 60,
95
94
  )
96
95
 
97
96
  # Get the API key from the agent's configuration
@@ -3,7 +3,6 @@
3
3
  import logging
4
4
  from typing import NotRequired, TypedDict
5
5
 
6
- from intentkit.abstracts.skill import SkillStoreABC
7
6
  from intentkit.skills.allora.base import AlloraBaseTool
8
7
  from intentkit.skills.allora.price import AlloraGetPrice
9
8
  from intentkit.skills.base import SkillConfig, SkillState
@@ -28,7 +27,6 @@ class Config(SkillConfig):
28
27
  async def get_skills(
29
28
  config: "Config",
30
29
  is_private: bool,
31
- store: SkillStoreABC,
32
30
  **_,
33
31
  ) -> list[AlloraBaseTool]:
34
32
  """Get all Allora skills.
@@ -36,7 +34,6 @@ async def get_skills(
36
34
  Args:
37
35
  config: The configuration for Allora skills.
38
36
  is_private: Whether to include private skills.
39
- store: The skill store for persisting data.
40
37
 
41
38
  Returns:
42
39
  A list of Allora skills.
@@ -53,7 +50,7 @@ async def get_skills(
53
50
  # Get each skill using the cached getter
54
51
  result = []
55
52
  for name in available_skills:
56
- skill = get_allora_skill(name, store)
53
+ skill = get_allora_skill(name)
57
54
  if skill:
58
55
  result.append(skill)
59
56
  return result
@@ -61,22 +58,18 @@ async def get_skills(
61
58
 
62
59
  def get_allora_skill(
63
60
  name: str,
64
- store: SkillStoreABC,
65
61
  ) -> AlloraBaseTool:
66
62
  """Get an Allora skill by name.
67
63
 
68
64
  Args:
69
65
  name: The name of the skill to get
70
- store: The skill store for persisting data
71
66
 
72
67
  Returns:
73
68
  The requested Allora skill
74
69
  """
75
70
  if name == "get_price_prediction":
76
71
  if name not in _cache:
77
- _cache[name] = AlloraGetPrice(
78
- skill_store=store,
79
- )
72
+ _cache[name] = AlloraGetPrice()
80
73
  return _cache[name]
81
74
  else:
82
75
  logger.warning(f"Unknown Allora skill: {name}")
@@ -3,7 +3,7 @@ from typing import Type
3
3
  from langchain_core.tools.base import ToolException
4
4
  from pydantic import BaseModel, Field
5
5
 
6
- from intentkit.abstracts.skill import SkillStoreABC
6
+ from intentkit.config.config import config
7
7
  from intentkit.skills.base import IntentKitSkill
8
8
 
9
9
  base_url = "https://api.upshot.xyz/v2/allora"
@@ -15,16 +15,13 @@ class AlloraBaseTool(IntentKitSkill):
15
15
  name: str = Field(description="The name of the tool")
16
16
  description: str = Field(description="A description of what the tool does")
17
17
  args_schema: Type[BaseModel]
18
- skill_store: SkillStoreABC = Field(
19
- description="The skill store for persisting data"
20
- )
21
18
 
22
19
  def get_api_key(self) -> str:
23
20
  context = self.get_context()
24
21
  skill_config = context.agent.skill_config(self.category)
25
22
  api_key_provider = skill_config.get("api_key_provider")
26
23
  if api_key_provider == "platform":
27
- return self.skill_store.get_system_config("allora_api_key")
24
+ return config.allora_api_key
28
25
  # for backward compatibility, may only have api_key in skill_config
29
26
  elif skill_config.get("api_key"):
30
27
  return skill_config.get("api_key")
intentkit/skills/base.py CHANGED
@@ -29,7 +29,6 @@ from redis.exceptions import RedisError
29
29
  from web3 import Web3
30
30
 
31
31
  from intentkit.abstracts.graph import AgentContext
32
- from intentkit.abstracts.skill import SkillStoreABC
33
32
  from intentkit.clients import get_wallet_provider
34
33
  from intentkit.clients.web3 import get_web3_client
35
34
  from intentkit.models.redis import get_redis
@@ -63,7 +62,6 @@ class IntentKitSkill(BaseTool):
63
62
  Will have predefined abilities.
64
63
  """
65
64
 
66
- skill_store: SkillStoreABC
67
65
  # overwrite the value of BaseTool
68
66
  handle_tool_error: Optional[Union[bool, str, Callable[[ToolException], str]]] = (
69
67
  lambda e: f"tool error: {e}"
@@ -84,15 +82,12 @@ class IntentKitSkill(BaseTool):
84
82
  """Get the category of the skill."""
85
83
  raise NotImplementedError
86
84
 
87
- async def user_rate_limit(
88
- self, user_id: str, limit: int, minutes: int, key: str
89
- ) -> None:
85
+ async def user_rate_limit(self, limit: int, seconds: int, key: str) -> None:
90
86
  """Check if a user has exceeded the rate limit for this skill.
91
87
 
92
88
  Args:
93
- user_id: The ID of the user to check
94
89
  limit: Maximum number of requests allowed
95
- minutes: Time window in minutes
90
+ seconds: Time window in seconds
96
91
  key: The key to use for rate limiting (e.g., skill name or category)
97
92
 
98
93
  Raises:
@@ -101,25 +96,48 @@ class IntentKitSkill(BaseTool):
101
96
  Returns:
102
97
  None: Always returns None if no exception is raised
103
98
  """
104
- if not user_id:
105
- return None # No rate limiting for users without ID
99
+ try:
100
+ context = self.get_context()
101
+ except ValueError:
102
+ self.logger.info(
103
+ "AgentContext not available, skipping rate limit for %s",
104
+ key,
105
+ )
106
+ return None
107
+
108
+ user_identifier = context.user_id or context.agent_id
109
+ if not user_identifier:
110
+ return None # No rate limiting when no identifier is available
111
+
112
+ try:
113
+ max_requests = int(limit)
114
+ window_seconds = int(seconds)
115
+ except (TypeError, ValueError):
116
+ self.logger.info(
117
+ "Invalid user rate limit parameters for %s: limit=%r, seconds=%r",
118
+ key,
119
+ limit,
120
+ seconds,
121
+ )
122
+ return None
123
+
124
+ if window_seconds <= 0 or max_requests <= 0:
125
+ return None
106
126
 
107
127
  try:
108
128
  redis = get_redis()
109
129
  # Create a unique key for this rate limit and user
110
- rate_limit_key = f"rate_limit:{key}:{user_id}"
130
+ rate_limit_key = f"rate_limit:{key}:{user_identifier}"
111
131
 
112
132
  # Get the current count
113
133
  count = await redis.incr(rate_limit_key)
114
134
 
115
135
  # Set expiration if this is the first request
116
136
  if count == 1:
117
- await redis.expire(
118
- rate_limit_key, minutes * 60
119
- ) # Convert minutes to seconds
137
+ await redis.expire(rate_limit_key, window_seconds)
120
138
 
121
139
  # Check if user has exceeded the limit
122
- if count > limit:
140
+ if count > max_requests:
123
141
  raise RateLimitExceeded(f"Rate limit exceeded for {key}")
124
142
 
125
143
  return None
@@ -135,40 +153,97 @@ class IntentKitSkill(BaseTool):
135
153
  )
136
154
  return None
137
155
 
138
- async def user_rate_limit_by_skill(
139
- self, user_id: str, limit: int, minutes: int
140
- ) -> None:
156
+ async def user_rate_limit_by_skill(self, limit: int, seconds: int) -> None:
141
157
  """Check if a user has exceeded the rate limit for this specific skill.
142
158
 
143
159
  This uses the skill name as the rate limit key.
144
160
 
145
161
  Args:
146
- user_id: The ID of the user to check
147
162
  limit: Maximum number of requests allowed
148
- minutes: Time window in minutes
163
+ seconds: Time window in seconds
149
164
 
150
165
  Raises:
151
166
  RateLimitExceeded: If the user has exceeded the rate limit
152
167
  """
153
- return await self.user_rate_limit(user_id, limit, minutes, self.name)
168
+ return await self.user_rate_limit(limit, seconds, self.name)
154
169
 
155
- async def user_rate_limit_by_category(
156
- self, user_id: str, limit: int, minutes: int
157
- ) -> None:
170
+ async def user_rate_limit_by_category(self, limit: int, seconds: int) -> None:
158
171
  """Check if a user has exceeded the rate limit for this skill category.
159
172
 
160
173
  This uses the skill category as the rate limit key, which means the limit
161
174
  is shared across all skills in the same category.
162
175
 
163
176
  Args:
164
- user_id: The ID of the user to check
165
177
  limit: Maximum number of requests allowed
166
- minutes: Time window in minutes
178
+ seconds: Time window in seconds
167
179
 
168
180
  Raises:
169
181
  RateLimitExceeded: If the user has exceeded the rate limit
170
182
  """
171
- return await self.user_rate_limit(user_id, limit, minutes, self.category)
183
+ return await self.user_rate_limit(limit, seconds, self.category)
184
+
185
+ async def global_rate_limit(self, limit: int, seconds: int, key: str) -> None:
186
+ """Check if a global rate limit has been exceeded for a given key.
187
+
188
+ Args:
189
+ limit: Maximum number of requests allowed
190
+ seconds: Time window in seconds
191
+ key: The key to use for rate limiting (e.g., skill name or category)
192
+
193
+ Raises:
194
+ RateLimitExceeded: If the global limit has been exceeded
195
+
196
+ Returns:
197
+ None: Always returns None if no exception is raised
198
+ """
199
+ try:
200
+ max_requests = int(limit)
201
+ window_seconds = int(seconds)
202
+ except (TypeError, ValueError):
203
+ self.logger.info(
204
+ "Invalid global rate limit parameters for %s: limit=%r, seconds=%r",
205
+ key,
206
+ limit,
207
+ seconds,
208
+ )
209
+ return None
210
+
211
+ if window_seconds <= 0 or max_requests <= 0:
212
+ return None
213
+
214
+ try:
215
+ redis = get_redis()
216
+ rate_limit_key = f"rate_limit:{key}"
217
+
218
+ count = await redis.incr(rate_limit_key)
219
+
220
+ if count == 1:
221
+ await redis.expire(rate_limit_key, window_seconds)
222
+
223
+ if count > max_requests:
224
+ raise RateLimitExceeded(f"Global rate limit exceeded for {key}")
225
+
226
+ return None
227
+
228
+ except RuntimeError:
229
+ self.logger.info(
230
+ "Redis not initialized, skipping global rate limit for %s",
231
+ key,
232
+ )
233
+ return None
234
+ except RedisError as e:
235
+ self.logger.info(
236
+ f"Redis error in global rate limiting: {e}, skipping rate limit for {key}"
237
+ )
238
+ return None
239
+
240
+ async def global_rate_limit_by_skill(self, limit: int, seconds: int) -> None:
241
+ """Apply a global rate limit scoped to this specific skill."""
242
+ return await self.global_rate_limit(limit, seconds, self.name)
243
+
244
+ async def global_rate_limit_by_category(self, limit: int, seconds: int) -> None:
245
+ """Apply a global rate limit scoped to this skill category."""
246
+ return await self.global_rate_limit(limit, seconds, self.category)
172
247
 
173
248
  def _run(self, *args: Any, **kwargs: Any) -> Any:
174
249
  raise NotImplementedError(
@@ -254,7 +329,6 @@ class IntentKitSkill(BaseTool):
254
329
 
255
330
  async def get_agentkit_actions(
256
331
  agent_id: str,
257
- _store: SkillStoreABC,
258
332
  provider_factories: Sequence[Callable[[], object]],
259
333
  *,
260
334
  agent: Optional["Agent"] = None,
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Optional, TypedDict
4
4
 
5
5
  from coinbase_agentkit import basename_action_provider
6
6
 
7
- from intentkit.abstracts.skill import SkillStoreABC
8
7
  from intentkit.skills.base import (
9
8
  SkillConfig,
10
9
  SkillState,
@@ -30,7 +29,6 @@ class Config(SkillConfig):
30
29
  async def get_skills(
31
30
  config: "Config",
32
31
  is_private: bool,
33
- store: SkillStoreABC,
34
32
  agent_id: str,
35
33
  agent: Optional["Agent"] = None,
36
34
  **_,
@@ -45,7 +43,7 @@ async def get_skills(
45
43
  available_skills.append(skill_name)
46
44
 
47
45
  actions = await get_agentkit_actions(
48
- agent_id, store, [basename_action_provider], agent=agent
46
+ agent_id, [basename_action_provider], agent=agent
49
47
  )
50
48
  tools: list[BasenameBaseTool] = []
51
49
  for skill in available_skills:
@@ -1,121 +1,116 @@
1
- import logging
2
- from typing import List, Literal, Optional, TypedDict
3
-
4
- from intentkit.abstracts.skill import SkillStoreABC
5
- from intentkit.skills.base import SkillConfig, SkillState
6
- from intentkit.skills.carv.base import CarvBaseTool
7
- from intentkit.skills.carv.fetch_news import FetchNewsTool
8
- from intentkit.skills.carv.onchain_query import OnchainQueryTool
9
- from intentkit.skills.carv.token_info_and_price import TokenInfoAndPriceTool
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- _cache: dict[str, CarvBaseTool] = {}
15
-
16
- _SKILL_NAME_TO_CLASS_MAP: dict[str, type[CarvBaseTool]] = {
17
- "onchain_query": OnchainQueryTool,
18
- "token_info_and_price": TokenInfoAndPriceTool,
19
- "fetch_news": FetchNewsTool,
20
- }
21
-
22
-
23
- class SkillStates(TypedDict):
24
- onchain_query: SkillState
25
- token_info_and_price: SkillState
26
- fetch_news: SkillState
27
-
28
-
29
- class Config(SkillConfig):
30
- enabled: bool
31
- states: SkillStates # type: ignore
32
- api_key_provider: Optional[Literal["agent_owner", "platform"]]
33
-
34
- # conditionally required
35
- api_key: Optional[str]
36
-
37
- # optional
38
- rate_limit_number: Optional[int]
39
- rate_limit_minutes: Optional[int]
40
-
41
-
42
- async def get_skills(
43
- config: "Config",
44
- is_private: bool,
45
- store: SkillStoreABC,
46
- **_,
47
- ) -> list[CarvBaseTool]:
48
- """
49
- Factory function to create and return CARV skill tools based on the provided configuration.
50
-
51
- Args:
52
- config: The configuration object for the CARV skill.
53
- is_private: A boolean indicating whether the request is from a private context.
54
- store: An instance of `SkillStoreABC`.
55
-
56
- Returns:
57
- A list of `CarvBaseTool` instances.
58
- """
59
- # Check if the entire category is disabled first
60
- if not config.get("enabled", False):
61
- return []
62
-
63
- available_skills: List[CarvBaseTool] = []
64
- skill_states = config.get("states", {})
65
-
66
- # Iterate through all known skills defined in the map
67
- for skill_name in _SKILL_NAME_TO_CLASS_MAP:
68
- state = skill_states.get(
69
- skill_name, "disabled"
70
- ) # Default to disabled if not in config
71
-
72
- if state == "disabled":
73
- continue
74
- elif state == "public" or (state == "private" and is_private):
75
- # If enabled, get the skill instance using the factory function
76
- skill_instance = get_carv_skill(skill_name, store)
77
- if skill_instance:
78
- available_skills.append(skill_instance)
79
- else:
80
- logger.warning(f"Could not instantiate known skill: {skill_name}")
81
-
82
- return available_skills
83
-
84
-
85
- def get_carv_skill(
86
- name: str,
87
- store: SkillStoreABC,
88
- ) -> Optional[CarvBaseTool]:
89
- """
90
- Factory function to retrieve a cached CARV skill instance by name.
91
-
92
- Args:
93
- name: The name of the CARV skill to retrieve.
94
- store: An instance of `SkillStoreABC`.
95
-
96
- Returns:
97
- The requested `CarvBaseTool` instance if found and enabled, otherwise None.
98
- """
99
-
100
- # Return from cache immediately if already exists
101
- if name in _cache:
102
- return _cache[name]
103
-
104
- # Get the class from the map
105
- skill_class = _SKILL_NAME_TO_CLASS_MAP.get(name)
106
-
107
- if skill_class:
108
- try:
109
- # Instantiate the skill and add to cache
110
- instance = skill_class(skill_store=store) # type: ignore
111
- _cache[name] = instance
112
- return instance
113
- except Exception as e:
114
- logger.error(
115
- f"Failed to instantiate Carv skill '{name}': {e}", exc_info=True
116
- )
117
- return None # Failed to instantiate
118
- else:
119
- # This handles cases where a name might be in config but not in our map
120
- logger.warning(f"Attempted to get unknown Carv skill: {name}")
121
- return None
1
+ import logging
2
+ from typing import List, Literal, Optional, TypedDict
3
+
4
+ from intentkit.skills.base import SkillConfig, SkillState
5
+ from intentkit.skills.carv.base import CarvBaseTool
6
+ from intentkit.skills.carv.fetch_news import FetchNewsTool
7
+ from intentkit.skills.carv.onchain_query import OnchainQueryTool
8
+ from intentkit.skills.carv.token_info_and_price import TokenInfoAndPriceTool
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ _cache: dict[str, CarvBaseTool] = {}
14
+
15
+ _SKILL_NAME_TO_CLASS_MAP: dict[str, type[CarvBaseTool]] = {
16
+ "onchain_query": OnchainQueryTool,
17
+ "token_info_and_price": TokenInfoAndPriceTool,
18
+ "fetch_news": FetchNewsTool,
19
+ }
20
+
21
+
22
+ class SkillStates(TypedDict):
23
+ onchain_query: SkillState
24
+ token_info_and_price: SkillState
25
+ fetch_news: SkillState
26
+
27
+
28
+ class Config(SkillConfig):
29
+ enabled: bool
30
+ states: SkillStates # type: ignore
31
+ api_key_provider: Optional[Literal["agent_owner", "platform"]]
32
+
33
+ # conditionally required
34
+ api_key: Optional[str]
35
+
36
+ # optional
37
+ rate_limit_number: Optional[int]
38
+ rate_limit_minutes: Optional[int]
39
+
40
+
41
+ async def get_skills(
42
+ config: "Config",
43
+ is_private: bool,
44
+ **_,
45
+ ) -> list[CarvBaseTool]:
46
+ """
47
+ Factory function to create and return CARV skill tools based on the provided configuration.
48
+
49
+ Args:
50
+ config: The configuration object for the CARV skill.
51
+ is_private: A boolean indicating whether the request is from a private context.
52
+
53
+ Returns:
54
+ A list of `CarvBaseTool` instances.
55
+ """
56
+ # Check if the entire category is disabled first
57
+ if not config.get("enabled", False):
58
+ return []
59
+
60
+ available_skills: List[CarvBaseTool] = []
61
+ skill_states = config.get("states", {})
62
+
63
+ # Iterate through all known skills defined in the map
64
+ for skill_name in _SKILL_NAME_TO_CLASS_MAP:
65
+ state = skill_states.get(
66
+ skill_name, "disabled"
67
+ ) # Default to disabled if not in config
68
+
69
+ if state == "disabled":
70
+ continue
71
+ elif state == "public" or (state == "private" and is_private):
72
+ # If enabled, get the skill instance using the factory function
73
+ skill_instance = get_carv_skill(skill_name)
74
+ if skill_instance:
75
+ available_skills.append(skill_instance)
76
+ else:
77
+ logger.warning(f"Could not instantiate known skill: {skill_name}")
78
+
79
+ return available_skills
80
+
81
+
82
+ def get_carv_skill(
83
+ name: str,
84
+ ) -> Optional[CarvBaseTool]:
85
+ """
86
+ Factory function to retrieve a cached CARV skill instance by name.
87
+
88
+ Args:
89
+ name: The name of the CARV skill to retrieve.
90
+
91
+ Returns:
92
+ The requested `CarvBaseTool` instance if found and enabled, otherwise None.
93
+ """
94
+
95
+ # Return from cache immediately if already exists
96
+ if name in _cache:
97
+ return _cache[name]
98
+
99
+ # Get the class from the map
100
+ skill_class = _SKILL_NAME_TO_CLASS_MAP.get(name)
101
+
102
+ if skill_class:
103
+ try:
104
+ # Instantiate the skill and add to cache
105
+ instance = skill_class() # type: ignore
106
+ _cache[name] = instance
107
+ return instance
108
+ except Exception as e:
109
+ logger.error(
110
+ f"Failed to instantiate Carv skill '{name}': {e}", exc_info=True
111
+ )
112
+ return None # Failed to instantiate
113
+ else:
114
+ # This handles cases where a name might be in config but not in our map
115
+ logger.warning(f"Attempted to get unknown Carv skill: {name}")
116
+ return None