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.
- intentkit/__init__.py +1 -1
- intentkit/abstracts/skill.py +2 -59
- intentkit/clients/twitter.py +35 -28
- intentkit/config/config.py +1 -0
- intentkit/core/agent.py +2 -279
- intentkit/core/asset.py +63 -16
- intentkit/core/engine.py +9 -5
- intentkit/core/scheduler.py +8 -8
- intentkit/models/agent.py +138 -94
- intentkit/models/agent_schema.json +6 -9
- intentkit/models/chat.py +1 -0
- intentkit/models/llm.csv +15 -12
- intentkit/skills/acolyt/__init__.py +2 -9
- intentkit/skills/acolyt/base.py +2 -5
- intentkit/skills/aixbt/__init__.py +2 -13
- intentkit/skills/aixbt/base.py +0 -4
- intentkit/skills/aixbt/projects.py +1 -2
- intentkit/skills/allora/__init__.py +2 -9
- intentkit/skills/allora/base.py +2 -5
- intentkit/skills/base.py +101 -27
- intentkit/skills/basename/__init__.py +1 -3
- intentkit/skills/carv/__init__.py +116 -121
- intentkit/skills/carv/base.py +184 -185
- intentkit/skills/casino/__init__.py +4 -15
- intentkit/skills/casino/base.py +0 -4
- intentkit/skills/casino/deck_draw.py +1 -2
- intentkit/skills/casino/deck_shuffle.py +1 -2
- intentkit/skills/casino/dice_roll.py +1 -2
- intentkit/skills/cdp/__init__.py +0 -5
- intentkit/skills/cdp/base.py +0 -4
- intentkit/skills/cdp/schema.json +1 -17
- intentkit/skills/chainlist/__init__.py +2 -7
- intentkit/skills/chainlist/base.py +0 -4
- intentkit/skills/common/__init__.py +2 -9
- intentkit/skills/common/base.py +0 -4
- intentkit/skills/cookiefun/__init__.py +6 -9
- intentkit/skills/cookiefun/base.py +0 -4
- intentkit/skills/cryptocompare/__init__.py +7 -24
- intentkit/skills/cryptocompare/base.py +0 -5
- intentkit/skills/cryptopanic/__init__.py +3 -6
- intentkit/skills/cryptopanic/base.py +53 -55
- intentkit/skills/cryptopanic/fetch_crypto_news.py +0 -2
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +1 -3
- intentkit/skills/dapplooker/__init__.py +2 -9
- intentkit/skills/dapplooker/base.py +2 -5
- intentkit/skills/defillama/__init__.py +24 -74
- intentkit/skills/defillama/base.py +0 -4
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +2 -2
- intentkit/skills/defillama/coins/fetch_block.py +2 -2
- intentkit/skills/defillama/coins/fetch_current_prices.py +2 -2
- intentkit/skills/defillama/coins/fetch_first_price.py +2 -2
- intentkit/skills/defillama/coins/fetch_historical_prices.py +2 -2
- intentkit/skills/defillama/coins/fetch_price_chart.py +2 -2
- intentkit/skills/defillama/coins/fetch_price_percentage.py +2 -2
- intentkit/skills/defillama/fees/fetch_fees_overview.py +2 -2
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +2 -2
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +2 -2
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +2 -2
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +2 -2
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -2
- intentkit/skills/defillama/tvl/fetch_chains.py +2 -2
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -2
- intentkit/skills/defillama/tvl/fetch_protocol.py +2 -2
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +2 -2
- intentkit/skills/defillama/tvl/fetch_protocols.py +2 -2
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +2 -2
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +2 -2
- intentkit/skills/defillama/volumes/fetch_options_overview.py +2 -2
- intentkit/skills/defillama/yields/fetch_pool_chart.py +2 -2
- intentkit/skills/defillama/yields/fetch_pools.py +2 -2
- intentkit/skills/dexscreener/__init__.py +97 -102
- intentkit/skills/dexscreener/base.py +125 -130
- intentkit/skills/dexscreener/get_pair_info.py +2 -3
- intentkit/skills/dexscreener/get_token_pairs.py +2 -3
- intentkit/skills/dexscreener/get_tokens_info.py +2 -3
- intentkit/skills/dexscreener/search_token.py +2 -4
- intentkit/skills/dune_analytics/__init__.py +4 -6
- intentkit/skills/dune_analytics/base.py +50 -52
- intentkit/skills/dune_analytics/fetch_kol_buys.py +0 -2
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +0 -2
- intentkit/skills/elfa/__init__.py +5 -18
- intentkit/skills/elfa/base.py +8 -10
- intentkit/skills/enso/__init__.py +9 -29
- intentkit/skills/enso/base.py +3 -6
- intentkit/skills/enso/route.py +1 -3
- intentkit/skills/erc20/__init__.py +1 -5
- intentkit/skills/erc721/__init__.py +1 -3
- intentkit/skills/firecrawl/__init__.py +5 -18
- intentkit/skills/firecrawl/base.py +2 -5
- intentkit/skills/firecrawl/crawl.py +10 -9
- intentkit/skills/firecrawl/query.py +3 -1
- intentkit/skills/firecrawl/scrape.py +8 -10
- intentkit/skills/firecrawl/utils.py +25 -26
- intentkit/skills/github/__init__.py +2 -7
- intentkit/skills/github/base.py +0 -4
- intentkit/skills/heurist/__init__.py +8 -27
- intentkit/skills/heurist/base.py +2 -5
- intentkit/skills/heurist/image_generation_animagine_xl.py +5 -5
- intentkit/skills/heurist/image_generation_arthemy_comics.py +5 -5
- intentkit/skills/heurist/image_generation_arthemy_real.py +5 -5
- intentkit/skills/heurist/image_generation_braindance.py +5 -5
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +5 -5
- intentkit/skills/heurist/image_generation_flux_1_dev.py +5 -5
- intentkit/skills/heurist/image_generation_sdxl.py +5 -5
- intentkit/skills/http/__init__.py +4 -15
- intentkit/skills/http/base.py +0 -4
- intentkit/skills/lifi/__init__.py +1 -6
- intentkit/skills/lifi/base.py +0 -4
- intentkit/skills/lifi/token_execute.py +1 -4
- intentkit/skills/lifi/token_quote.py +1 -3
- intentkit/skills/moralis/__init__.py +3 -7
- intentkit/skills/moralis/base.py +2 -5
- intentkit/skills/morpho/__init__.py +1 -3
- intentkit/skills/nation/__init__.py +2 -7
- intentkit/skills/nation/base.py +4 -7
- intentkit/skills/openai/__init__.py +5 -18
- intentkit/skills/openai/base.py +8 -10
- intentkit/skills/openai/dalle_image_generation.py +2 -5
- intentkit/skills/openai/gpt_image_generation.py +2 -5
- intentkit/skills/openai/gpt_image_to_image.py +2 -5
- intentkit/skills/openai/image_to_text.py +2 -5
- intentkit/skills/portfolio/__init__.py +11 -35
- intentkit/skills/portfolio/base.py +2 -5
- intentkit/skills/pyth/__init__.py +1 -5
- intentkit/skills/slack/__init__.py +5 -17
- intentkit/skills/slack/base.py +0 -4
- intentkit/skills/supabase/__init__.py +7 -23
- intentkit/skills/supabase/base.py +0 -4
- intentkit/skills/superfluid/__init__.py +1 -3
- intentkit/skills/system/__init__.py +7 -24
- intentkit/skills/system/add_autonomous_task.py +2 -2
- intentkit/skills/system/delete_autonomous_task.py +2 -2
- intentkit/skills/system/edit_autonomous_task.py +2 -4
- intentkit/skills/system/list_autonomous_tasks.py +2 -2
- intentkit/skills/system/read_agent_api_key.py +6 -4
- intentkit/skills/system/regenerate_agent_api_key.py +6 -4
- intentkit/skills/tavily/__init__.py +3 -12
- intentkit/skills/tavily/base.py +2 -5
- intentkit/skills/tavily/tavily_extract.py +1 -2
- intentkit/skills/tavily/tavily_search.py +3 -3
- intentkit/skills/token/__init__.py +5 -10
- intentkit/skills/token/base.py +2 -6
- intentkit/skills/twitter/__init__.py +11 -35
- intentkit/skills/twitter/base.py +14 -16
- intentkit/skills/twitter/follow_user.py +0 -1
- intentkit/skills/twitter/get_mentions.py +0 -1
- intentkit/skills/twitter/get_timeline.py +0 -1
- intentkit/skills/twitter/get_user_by_username.py +0 -1
- intentkit/skills/twitter/get_user_tweets.py +0 -1
- intentkit/skills/twitter/like_tweet.py +0 -1
- intentkit/skills/twitter/post_tweet.py +2 -2
- intentkit/skills/twitter/reply_tweet.py +2 -2
- intentkit/skills/twitter/retweet.py +0 -1
- intentkit/skills/twitter/search_tweets.py +0 -1
- intentkit/skills/unrealspeech/__init__.py +2 -7
- intentkit/skills/unrealspeech/base.py +0 -4
- intentkit/skills/venice_audio/__init__.py +99 -106
- intentkit/skills/venice_audio/base.py +118 -121
- intentkit/skills/venice_audio/venice_audio.py +1 -5
- intentkit/skills/venice_image/__init__.py +147 -154
- intentkit/skills/venice_image/base.py +185 -192
- intentkit/skills/web_scraper/__init__.py +5 -18
- intentkit/skills/web_scraper/base.py +20 -4
- intentkit/skills/web_scraper/document_indexer.py +6 -4
- intentkit/skills/web_scraper/scrape_and_index.py +11 -8
- intentkit/skills/web_scraper/utils.py +31 -27
- intentkit/skills/web_scraper/website_indexer.py +7 -8
- intentkit/skills/weth/__init__.py +1 -5
- intentkit/skills/wow/__init__.py +1 -5
- intentkit/skills/xmtp/__init__.py +4 -15
- intentkit/utils/schema.py +100 -0
- {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev1.dist-info}/METADATA +1 -1
- {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev1.dist-info}/RECORD +175 -174
- {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev1.dist-info}/WHEEL +0 -0
- {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}")
|
intentkit/skills/aixbt/base.py
CHANGED
|
@@ -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
|
|
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}")
|
intentkit/skills/allora/base.py
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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}:{
|
|
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 >
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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.
|
|
5
|
-
from intentkit.skills.base import
|
|
6
|
-
from intentkit.skills.carv.
|
|
7
|
-
from intentkit.skills.carv.
|
|
8
|
-
from intentkit.skills.carv.
|
|
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
|
-
name
|
|
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
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|