intentkit 0.8.11.dev1__py3-none-any.whl → 0.8.12__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 (183) hide show
  1. intentkit/__init__.py +1 -1
  2. intentkit/abstracts/graph.py +4 -0
  3. intentkit/abstracts/skill.py +2 -140
  4. intentkit/clients/twitter.py +35 -28
  5. intentkit/core/agent.py +2 -374
  6. intentkit/core/asset.py +63 -16
  7. intentkit/core/engine.py +16 -7
  8. intentkit/core/scheduler.py +8 -8
  9. intentkit/models/agent.py +109 -94
  10. intentkit/models/agent_schema.json +6 -9
  11. intentkit/models/llm.csv +15 -12
  12. intentkit/models/skill.py +38 -40
  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 +168 -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 +4 -6
  27. intentkit/skills/casino/deck_shuffle.py +5 -4
  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 +4 -18
  40. intentkit/skills/cryptocompare/fetch_news.py +1 -1
  41. intentkit/skills/cryptocompare/fetch_price.py +1 -1
  42. intentkit/skills/cryptocompare/fetch_top_exchanges.py +1 -1
  43. intentkit/skills/cryptocompare/fetch_top_market_cap.py +1 -1
  44. intentkit/skills/cryptocompare/fetch_top_volume.py +1 -1
  45. intentkit/skills/cryptocompare/fetch_trading_signals.py +1 -1
  46. intentkit/skills/cryptopanic/__init__.py +3 -6
  47. intentkit/skills/cryptopanic/base.py +53 -55
  48. intentkit/skills/cryptopanic/fetch_crypto_news.py +0 -2
  49. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +1 -3
  50. intentkit/skills/dapplooker/__init__.py +2 -9
  51. intentkit/skills/dapplooker/base.py +2 -5
  52. intentkit/skills/defillama/__init__.py +24 -74
  53. intentkit/skills/defillama/base.py +3 -13
  54. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +2 -2
  55. intentkit/skills/defillama/coins/fetch_block.py +2 -2
  56. intentkit/skills/defillama/coins/fetch_current_prices.py +2 -2
  57. intentkit/skills/defillama/coins/fetch_first_price.py +2 -2
  58. intentkit/skills/defillama/coins/fetch_historical_prices.py +2 -2
  59. intentkit/skills/defillama/coins/fetch_price_chart.py +2 -2
  60. intentkit/skills/defillama/coins/fetch_price_percentage.py +2 -2
  61. intentkit/skills/defillama/fees/fetch_fees_overview.py +2 -2
  62. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +2 -2
  63. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +2 -2
  64. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +2 -2
  65. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +2 -2
  66. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -2
  67. intentkit/skills/defillama/tvl/fetch_chains.py +2 -2
  68. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -2
  69. intentkit/skills/defillama/tvl/fetch_protocol.py +2 -2
  70. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +2 -2
  71. intentkit/skills/defillama/tvl/fetch_protocols.py +2 -2
  72. intentkit/skills/defillama/volumes/fetch_dex_overview.py +2 -2
  73. intentkit/skills/defillama/volumes/fetch_dex_summary.py +2 -2
  74. intentkit/skills/defillama/volumes/fetch_options_overview.py +2 -2
  75. intentkit/skills/defillama/yields/fetch_pool_chart.py +2 -2
  76. intentkit/skills/defillama/yields/fetch_pools.py +2 -2
  77. intentkit/skills/dexscreener/__init__.py +97 -102
  78. intentkit/skills/dexscreener/base.py +125 -130
  79. intentkit/skills/dexscreener/get_pair_info.py +2 -3
  80. intentkit/skills/dexscreener/get_token_pairs.py +2 -3
  81. intentkit/skills/dexscreener/get_tokens_info.py +2 -3
  82. intentkit/skills/dexscreener/search_token.py +2 -4
  83. intentkit/skills/dune_analytics/__init__.py +4 -6
  84. intentkit/skills/dune_analytics/base.py +50 -52
  85. intentkit/skills/dune_analytics/fetch_kol_buys.py +0 -2
  86. intentkit/skills/dune_analytics/fetch_nation_metrics.py +0 -2
  87. intentkit/skills/elfa/__init__.py +5 -18
  88. intentkit/skills/elfa/base.py +8 -10
  89. intentkit/skills/enso/__init__.py +9 -29
  90. intentkit/skills/enso/base.py +3 -6
  91. intentkit/skills/enso/networks.py +1 -6
  92. intentkit/skills/enso/route.py +4 -8
  93. intentkit/skills/enso/tokens.py +2 -12
  94. intentkit/skills/erc20/__init__.py +1 -5
  95. intentkit/skills/erc721/__init__.py +1 -3
  96. intentkit/skills/firecrawl/__init__.py +5 -18
  97. intentkit/skills/firecrawl/base.py +2 -5
  98. intentkit/skills/firecrawl/clear.py +3 -6
  99. intentkit/skills/firecrawl/crawl.py +10 -9
  100. intentkit/skills/firecrawl/query.py +3 -1
  101. intentkit/skills/firecrawl/scrape.py +10 -14
  102. intentkit/skills/firecrawl/utils.py +39 -31
  103. intentkit/skills/github/__init__.py +2 -7
  104. intentkit/skills/github/base.py +0 -4
  105. intentkit/skills/heurist/__init__.py +8 -27
  106. intentkit/skills/heurist/base.py +2 -5
  107. intentkit/skills/heurist/image_generation_animagine_xl.py +5 -5
  108. intentkit/skills/heurist/image_generation_arthemy_comics.py +5 -5
  109. intentkit/skills/heurist/image_generation_arthemy_real.py +5 -5
  110. intentkit/skills/heurist/image_generation_braindance.py +5 -5
  111. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +5 -5
  112. intentkit/skills/heurist/image_generation_flux_1_dev.py +5 -5
  113. intentkit/skills/heurist/image_generation_sdxl.py +5 -5
  114. intentkit/skills/http/__init__.py +4 -15
  115. intentkit/skills/http/base.py +0 -4
  116. intentkit/skills/lifi/__init__.py +1 -6
  117. intentkit/skills/lifi/base.py +0 -4
  118. intentkit/skills/lifi/token_execute.py +1 -4
  119. intentkit/skills/lifi/token_quote.py +1 -3
  120. intentkit/skills/moralis/__init__.py +3 -7
  121. intentkit/skills/moralis/base.py +2 -5
  122. intentkit/skills/morpho/__init__.py +1 -3
  123. intentkit/skills/nation/__init__.py +2 -7
  124. intentkit/skills/nation/base.py +4 -7
  125. intentkit/skills/openai/__init__.py +5 -18
  126. intentkit/skills/openai/base.py +8 -10
  127. intentkit/skills/openai/dalle_image_generation.py +2 -5
  128. intentkit/skills/openai/gpt_image_generation.py +2 -5
  129. intentkit/skills/openai/gpt_image_to_image.py +2 -5
  130. intentkit/skills/openai/image_to_text.py +2 -5
  131. intentkit/skills/portfolio/__init__.py +11 -35
  132. intentkit/skills/portfolio/base.py +2 -5
  133. intentkit/skills/pyth/__init__.py +1 -5
  134. intentkit/skills/slack/__init__.py +5 -17
  135. intentkit/skills/slack/base.py +0 -4
  136. intentkit/skills/supabase/__init__.py +7 -23
  137. intentkit/skills/supabase/base.py +0 -4
  138. intentkit/skills/superfluid/__init__.py +1 -3
  139. intentkit/skills/system/__init__.py +7 -24
  140. intentkit/skills/system/add_autonomous_task.py +2 -2
  141. intentkit/skills/system/delete_autonomous_task.py +2 -2
  142. intentkit/skills/system/edit_autonomous_task.py +2 -4
  143. intentkit/skills/system/list_autonomous_tasks.py +2 -2
  144. intentkit/skills/system/read_agent_api_key.py +6 -4
  145. intentkit/skills/system/regenerate_agent_api_key.py +6 -4
  146. intentkit/skills/tavily/__init__.py +3 -12
  147. intentkit/skills/tavily/base.py +2 -5
  148. intentkit/skills/tavily/tavily_extract.py +1 -2
  149. intentkit/skills/tavily/tavily_search.py +3 -3
  150. intentkit/skills/token/__init__.py +5 -10
  151. intentkit/skills/token/base.py +2 -6
  152. intentkit/skills/twitter/__init__.py +11 -35
  153. intentkit/skills/twitter/base.py +18 -29
  154. intentkit/skills/twitter/follow_user.py +1 -4
  155. intentkit/skills/twitter/get_mentions.py +2 -8
  156. intentkit/skills/twitter/get_timeline.py +3 -10
  157. intentkit/skills/twitter/get_user_by_username.py +1 -4
  158. intentkit/skills/twitter/get_user_tweets.py +3 -10
  159. intentkit/skills/twitter/like_tweet.py +1 -4
  160. intentkit/skills/twitter/post_tweet.py +3 -5
  161. intentkit/skills/twitter/reply_tweet.py +3 -5
  162. intentkit/skills/twitter/retweet.py +1 -4
  163. intentkit/skills/twitter/search_tweets.py +3 -10
  164. intentkit/skills/unrealspeech/__init__.py +2 -7
  165. intentkit/skills/unrealspeech/base.py +0 -4
  166. intentkit/skills/venice_audio/__init__.py +99 -106
  167. intentkit/skills/venice_audio/base.py +118 -121
  168. intentkit/skills/venice_audio/venice_audio.py +1 -5
  169. intentkit/skills/venice_image/__init__.py +147 -154
  170. intentkit/skills/venice_image/base.py +185 -192
  171. intentkit/skills/web_scraper/__init__.py +5 -18
  172. intentkit/skills/web_scraper/base.py +20 -4
  173. intentkit/skills/web_scraper/document_indexer.py +6 -4
  174. intentkit/skills/web_scraper/scrape_and_index.py +11 -10
  175. intentkit/skills/web_scraper/utils.py +38 -38
  176. intentkit/skills/web_scraper/website_indexer.py +7 -8
  177. intentkit/skills/weth/__init__.py +1 -5
  178. intentkit/skills/wow/__init__.py +1 -5
  179. intentkit/skills/xmtp/__init__.py +4 -15
  180. {intentkit-0.8.11.dev1.dist-info → intentkit-0.8.12.dist-info}/METADATA +1 -1
  181. {intentkit-0.8.11.dev1.dist-info → intentkit-0.8.12.dist-info}/RECORD +183 -183
  182. {intentkit-0.8.11.dev1.dist-info → intentkit-0.8.12.dist-info}/WHEEL +0 -0
  183. {intentkit-0.8.11.dev1.dist-info → intentkit-0.8.12.dist-info}/licenses/LICENSE +0 -0
@@ -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://acolyt-oracle-poc.vercel.app"
@@ -15,16 +15,13 @@ class AcolytBaseTool(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("acolyt_api_key")
24
+ return config.acolyt_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")
@@ -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,10 +29,15 @@ 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
35
+ from intentkit.models.skill import (
36
+ AgentSkillData,
37
+ AgentSkillDataCreate,
38
+ ChatSkillData,
39
+ ChatSkillDataCreate,
40
+ )
36
41
  from intentkit.utils.error import IntentKitAPIError, RateLimitExceeded
37
42
 
38
43
  if TYPE_CHECKING:
@@ -57,7 +62,6 @@ class IntentKitSkill(BaseTool):
57
62
  Will have predefined abilities.
58
63
  """
59
64
 
60
- skill_store: SkillStoreABC
61
65
  # overwrite the value of BaseTool
62
66
  handle_tool_error: Optional[Union[bool, str, Callable[[ToolException], str]]] = (
63
67
  lambda e: f"tool error: {e}"
@@ -78,15 +82,12 @@ class IntentKitSkill(BaseTool):
78
82
  """Get the category of the skill."""
79
83
  raise NotImplementedError
80
84
 
81
- async def user_rate_limit(
82
- self, user_id: str, limit: int, minutes: int, key: str
83
- ) -> None:
85
+ async def user_rate_limit(self, limit: int, seconds: int, key: str) -> None:
84
86
  """Check if a user has exceeded the rate limit for this skill.
85
87
 
86
88
  Args:
87
- user_id: The ID of the user to check
88
89
  limit: Maximum number of requests allowed
89
- minutes: Time window in minutes
90
+ seconds: Time window in seconds
90
91
  key: The key to use for rate limiting (e.g., skill name or category)
91
92
 
92
93
  Raises:
@@ -95,25 +96,48 @@ class IntentKitSkill(BaseTool):
95
96
  Returns:
96
97
  None: Always returns None if no exception is raised
97
98
  """
98
- if not user_id:
99
- 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
100
126
 
101
127
  try:
102
128
  redis = get_redis()
103
129
  # Create a unique key for this rate limit and user
104
- rate_limit_key = f"rate_limit:{key}:{user_id}"
130
+ rate_limit_key = f"rate_limit:{key}:{user_identifier}"
105
131
 
106
132
  # Get the current count
107
133
  count = await redis.incr(rate_limit_key)
108
134
 
109
135
  # Set expiration if this is the first request
110
136
  if count == 1:
111
- await redis.expire(
112
- rate_limit_key, minutes * 60
113
- ) # Convert minutes to seconds
137
+ await redis.expire(rate_limit_key, window_seconds)
114
138
 
115
139
  # Check if user has exceeded the limit
116
- if count > limit:
140
+ if count > max_requests:
117
141
  raise RateLimitExceeded(f"Rate limit exceeded for {key}")
118
142
 
119
143
  return None
@@ -129,40 +153,97 @@ class IntentKitSkill(BaseTool):
129
153
  )
130
154
  return None
131
155
 
132
- async def user_rate_limit_by_skill(
133
- self, user_id: str, limit: int, minutes: int
134
- ) -> None:
156
+ async def user_rate_limit_by_skill(self, limit: int, seconds: int) -> None:
135
157
  """Check if a user has exceeded the rate limit for this specific skill.
136
158
 
137
159
  This uses the skill name as the rate limit key.
138
160
 
139
161
  Args:
140
- user_id: The ID of the user to check
141
162
  limit: Maximum number of requests allowed
142
- minutes: Time window in minutes
163
+ seconds: Time window in seconds
143
164
 
144
165
  Raises:
145
166
  RateLimitExceeded: If the user has exceeded the rate limit
146
167
  """
147
- return await self.user_rate_limit(user_id, limit, minutes, self.name)
168
+ return await self.user_rate_limit(limit, seconds, self.name)
148
169
 
149
- async def user_rate_limit_by_category(
150
- self, user_id: str, limit: int, minutes: int
151
- ) -> None:
170
+ async def user_rate_limit_by_category(self, limit: int, seconds: int) -> None:
152
171
  """Check if a user has exceeded the rate limit for this skill category.
153
172
 
154
173
  This uses the skill category as the rate limit key, which means the limit
155
174
  is shared across all skills in the same category.
156
175
 
157
176
  Args:
158
- user_id: The ID of the user to check
159
177
  limit: Maximum number of requests allowed
160
- minutes: Time window in minutes
178
+ seconds: Time window in seconds
161
179
 
162
180
  Raises:
163
181
  RateLimitExceeded: If the user has exceeded the rate limit
164
182
  """
165
- 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)
166
247
 
167
248
  def _run(self, *args: Any, **kwargs: Any) -> Any:
168
249
  raise NotImplementedError(
@@ -184,10 +265,70 @@ class IntentKitSkill(BaseTool):
184
265
 
185
266
  return get_web3_client(network_id)
186
267
 
268
+ async def get_agent_skill_data(
269
+ self,
270
+ key: str,
271
+ ) -> Optional[Dict[str, Any]]:
272
+ """Retrieve persisted data for this skill scoped to the active agent."""
273
+ return await self.get_agent_skill_data_raw(self.name, key)
274
+
275
+ async def get_agent_skill_data_raw(
276
+ self,
277
+ skill_name: str,
278
+ key: str,
279
+ ) -> Optional[Dict[str, Any]]:
280
+ """Retrieve persisted data for a specific skill scoped to the active agent."""
281
+ context = self.get_context()
282
+ return await AgentSkillData.get(context.agent_id, skill_name, key)
283
+
284
+ async def save_agent_skill_data(self, key: str, data: Dict[str, Any]) -> None:
285
+ """Persist data for this skill scoped to the active agent."""
286
+ await self.save_agent_skill_data_raw(self.name, key, data)
287
+
288
+ async def save_agent_skill_data_raw(
289
+ self,
290
+ skill_name: str,
291
+ key: str,
292
+ data: Dict[str, Any],
293
+ ) -> None:
294
+ """Persist data for a specific skill scoped to the active agent."""
295
+ context = self.get_context()
296
+ skill_data = AgentSkillDataCreate(
297
+ agent_id=context.agent_id,
298
+ skill=skill_name,
299
+ key=key,
300
+ data=data,
301
+ )
302
+ await skill_data.save()
303
+
304
+ async def delete_agent_skill_data(self, key: str) -> None:
305
+ """Remove persisted data for this skill scoped to the active agent."""
306
+ context = self.get_context()
307
+ await AgentSkillData.delete(context.agent_id, self.name, key)
308
+
309
+ async def get_thread_skill_data(
310
+ self,
311
+ key: str,
312
+ ) -> Optional[Dict[str, Any]]:
313
+ """Retrieve persisted data for this skill scoped to the active chat."""
314
+ context = self.get_context()
315
+ return await ChatSkillData.get(context.chat_id, self.name, key)
316
+
317
+ async def save_thread_skill_data(self, key: str, data: Dict[str, Any]) -> None:
318
+ """Persist data for this skill scoped to the active chat."""
319
+ context = self.get_context()
320
+ skill_data = ChatSkillDataCreate(
321
+ chat_id=context.chat_id,
322
+ agent_id=context.agent_id,
323
+ skill=self.name,
324
+ key=key,
325
+ data=data,
326
+ )
327
+ await skill_data.save()
328
+
187
329
 
188
330
  async def get_agentkit_actions(
189
331
  agent_id: str,
190
- _store: SkillStoreABC,
191
332
  provider_factories: Sequence[Callable[[], object]],
192
333
  *,
193
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: