intentkit 0.5.0__py3-none-any.whl → 0.5.2__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 +17 -0
- intentkit/abstracts/__init__.py +0 -0
- intentkit/abstracts/agent.py +60 -0
- intentkit/abstracts/api.py +4 -0
- intentkit/abstracts/engine.py +38 -0
- intentkit/abstracts/exception.py +9 -0
- intentkit/abstracts/graph.py +25 -0
- intentkit/abstracts/skill.py +129 -0
- intentkit/abstracts/twitter.py +54 -0
- intentkit/clients/__init__.py +14 -0
- intentkit/clients/cdp.py +53 -0
- intentkit/clients/twitter.py +445 -0
- intentkit/config/__init__.py +0 -0
- intentkit/config/config.py +164 -0
- intentkit/core/__init__.py +0 -0
- intentkit/core/agent.py +191 -0
- intentkit/core/api.py +40 -0
- intentkit/core/client.py +45 -0
- intentkit/core/credit.py +1767 -0
- intentkit/core/engine.py +1018 -0
- intentkit/core/node.py +223 -0
- intentkit/core/prompt.py +58 -0
- intentkit/core/skill.py +124 -0
- intentkit/models/agent.py +1689 -0
- intentkit/models/agent_data.py +810 -0
- intentkit/models/agent_schema.json +733 -0
- intentkit/models/app_setting.py +156 -0
- intentkit/models/base.py +9 -0
- intentkit/models/chat.py +581 -0
- intentkit/models/conversation.py +286 -0
- intentkit/models/credit.py +1406 -0
- intentkit/models/db.py +120 -0
- intentkit/models/db_mig.py +102 -0
- intentkit/models/generator.py +347 -0
- intentkit/models/llm.py +746 -0
- intentkit/models/redis.py +132 -0
- intentkit/models/skill.py +466 -0
- intentkit/models/user.py +243 -0
- intentkit/skills/__init__.py +12 -0
- intentkit/skills/acolyt/__init__.py +83 -0
- intentkit/skills/acolyt/acolyt.jpg +0 -0
- intentkit/skills/acolyt/ask.py +128 -0
- intentkit/skills/acolyt/base.py +28 -0
- intentkit/skills/acolyt/schema.json +89 -0
- intentkit/skills/aixbt/README.md +71 -0
- intentkit/skills/aixbt/__init__.py +73 -0
- intentkit/skills/aixbt/aixbt.jpg +0 -0
- intentkit/skills/aixbt/base.py +21 -0
- intentkit/skills/aixbt/projects.py +153 -0
- intentkit/skills/aixbt/schema.json +99 -0
- intentkit/skills/allora/__init__.py +83 -0
- intentkit/skills/allora/allora.jpeg +0 -0
- intentkit/skills/allora/base.py +28 -0
- intentkit/skills/allora/price.py +130 -0
- intentkit/skills/allora/schema.json +89 -0
- intentkit/skills/base.py +174 -0
- intentkit/skills/carv/README.md +95 -0
- intentkit/skills/carv/__init__.py +121 -0
- intentkit/skills/carv/base.py +183 -0
- intentkit/skills/carv/carv.webp +0 -0
- intentkit/skills/carv/fetch_news.py +92 -0
- intentkit/skills/carv/onchain_query.py +164 -0
- intentkit/skills/carv/schema.json +137 -0
- intentkit/skills/carv/token_info_and_price.py +110 -0
- intentkit/skills/cdp/__init__.py +137 -0
- intentkit/skills/cdp/base.py +21 -0
- intentkit/skills/cdp/cdp.png +0 -0
- intentkit/skills/cdp/get_balance.py +81 -0
- intentkit/skills/cdp/schema.json +473 -0
- intentkit/skills/chainlist/README.md +38 -0
- intentkit/skills/chainlist/__init__.py +54 -0
- intentkit/skills/chainlist/base.py +21 -0
- intentkit/skills/chainlist/chain_lookup.py +208 -0
- intentkit/skills/chainlist/chainlist.png +0 -0
- intentkit/skills/chainlist/schema.json +47 -0
- intentkit/skills/common/__init__.py +82 -0
- intentkit/skills/common/base.py +21 -0
- intentkit/skills/common/common.jpg +0 -0
- intentkit/skills/common/current_time.py +84 -0
- intentkit/skills/common/schema.json +57 -0
- intentkit/skills/cookiefun/README.md +121 -0
- intentkit/skills/cookiefun/__init__.py +78 -0
- intentkit/skills/cookiefun/base.py +41 -0
- intentkit/skills/cookiefun/constants.py +18 -0
- intentkit/skills/cookiefun/cookiefun.png +0 -0
- intentkit/skills/cookiefun/get_account_details.py +171 -0
- intentkit/skills/cookiefun/get_account_feed.py +282 -0
- intentkit/skills/cookiefun/get_account_smart_followers.py +181 -0
- intentkit/skills/cookiefun/get_sectors.py +128 -0
- intentkit/skills/cookiefun/schema.json +155 -0
- intentkit/skills/cookiefun/search_accounts.py +225 -0
- intentkit/skills/cryptocompare/__init__.py +130 -0
- intentkit/skills/cryptocompare/api.py +159 -0
- intentkit/skills/cryptocompare/base.py +303 -0
- intentkit/skills/cryptocompare/cryptocompare.png +0 -0
- intentkit/skills/cryptocompare/fetch_news.py +96 -0
- intentkit/skills/cryptocompare/fetch_price.py +99 -0
- intentkit/skills/cryptocompare/fetch_top_exchanges.py +113 -0
- intentkit/skills/cryptocompare/fetch_top_market_cap.py +109 -0
- intentkit/skills/cryptocompare/fetch_top_volume.py +108 -0
- intentkit/skills/cryptocompare/fetch_trading_signals.py +107 -0
- intentkit/skills/cryptocompare/schema.json +168 -0
- intentkit/skills/cryptopanic/__init__.py +108 -0
- intentkit/skills/cryptopanic/base.py +51 -0
- intentkit/skills/cryptopanic/cryptopanic.png +0 -0
- intentkit/skills/cryptopanic/fetch_crypto_news.py +153 -0
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +136 -0
- intentkit/skills/cryptopanic/schema.json +103 -0
- intentkit/skills/dapplooker/README.md +92 -0
- intentkit/skills/dapplooker/__init__.py +83 -0
- intentkit/skills/dapplooker/base.py +26 -0
- intentkit/skills/dapplooker/dapplooker.jpg +0 -0
- intentkit/skills/dapplooker/dapplooker_token_data.py +476 -0
- intentkit/skills/dapplooker/schema.json +91 -0
- intentkit/skills/defillama/__init__.py +323 -0
- intentkit/skills/defillama/api.py +315 -0
- intentkit/skills/defillama/base.py +135 -0
- intentkit/skills/defillama/coins/__init__.py +0 -0
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +116 -0
- intentkit/skills/defillama/coins/fetch_block.py +98 -0
- intentkit/skills/defillama/coins/fetch_current_prices.py +105 -0
- intentkit/skills/defillama/coins/fetch_first_price.py +100 -0
- intentkit/skills/defillama/coins/fetch_historical_prices.py +110 -0
- intentkit/skills/defillama/coins/fetch_price_chart.py +109 -0
- intentkit/skills/defillama/coins/fetch_price_percentage.py +93 -0
- intentkit/skills/defillama/config/__init__.py +0 -0
- intentkit/skills/defillama/config/chains.py +433 -0
- intentkit/skills/defillama/defillama.jpeg +0 -0
- intentkit/skills/defillama/fees/__init__.py +0 -0
- intentkit/skills/defillama/fees/fetch_fees_overview.py +130 -0
- intentkit/skills/defillama/schema.json +383 -0
- intentkit/skills/defillama/stablecoins/__init__.py +0 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +100 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +129 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +83 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +126 -0
- intentkit/skills/defillama/tests/__init__.py +0 -0
- intentkit/skills/defillama/tests/api_integration.test.py +192 -0
- intentkit/skills/defillama/tests/api_unit.test.py +583 -0
- intentkit/skills/defillama/tvl/__init__.py +0 -0
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +106 -0
- intentkit/skills/defillama/tvl/fetch_chains.py +107 -0
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +91 -0
- intentkit/skills/defillama/tvl/fetch_protocol.py +207 -0
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +93 -0
- intentkit/skills/defillama/tvl/fetch_protocols.py +196 -0
- intentkit/skills/defillama/volumes/__init__.py +0 -0
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +157 -0
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +123 -0
- intentkit/skills/defillama/volumes/fetch_options_overview.py +131 -0
- intentkit/skills/defillama/yields/__init__.py +0 -0
- intentkit/skills/defillama/yields/fetch_pool_chart.py +100 -0
- intentkit/skills/defillama/yields/fetch_pools.py +126 -0
- intentkit/skills/dexscreener/__init__.py +93 -0
- intentkit/skills/dexscreener/base.py +133 -0
- intentkit/skills/dexscreener/dexscreener.png +0 -0
- intentkit/skills/dexscreener/model/__init__.py +0 -0
- intentkit/skills/dexscreener/model/search_token_response.py +82 -0
- intentkit/skills/dexscreener/schema.json +48 -0
- intentkit/skills/dexscreener/search_token.py +321 -0
- intentkit/skills/dune_analytics/__init__.py +103 -0
- intentkit/skills/dune_analytics/base.py +46 -0
- intentkit/skills/dune_analytics/dune.png +0 -0
- intentkit/skills/dune_analytics/fetch_kol_buys.py +128 -0
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +237 -0
- intentkit/skills/dune_analytics/schema.json +99 -0
- intentkit/skills/elfa/README.md +100 -0
- intentkit/skills/elfa/__init__.py +123 -0
- intentkit/skills/elfa/base.py +28 -0
- intentkit/skills/elfa/elfa.jpg +0 -0
- intentkit/skills/elfa/mention.py +504 -0
- intentkit/skills/elfa/schema.json +153 -0
- intentkit/skills/elfa/stats.py +118 -0
- intentkit/skills/elfa/tokens.py +126 -0
- intentkit/skills/enso/README.md +75 -0
- intentkit/skills/enso/__init__.py +114 -0
- intentkit/skills/enso/abi/__init__.py +0 -0
- intentkit/skills/enso/abi/approval.py +279 -0
- intentkit/skills/enso/abi/erc20.py +14 -0
- intentkit/skills/enso/abi/route.py +129 -0
- intentkit/skills/enso/base.py +44 -0
- intentkit/skills/enso/best_yield.py +286 -0
- intentkit/skills/enso/enso.jpg +0 -0
- intentkit/skills/enso/networks.py +105 -0
- intentkit/skills/enso/prices.py +93 -0
- intentkit/skills/enso/route.py +300 -0
- intentkit/skills/enso/schema.json +212 -0
- intentkit/skills/enso/tokens.py +223 -0
- intentkit/skills/enso/wallet.py +381 -0
- intentkit/skills/github/README.md +63 -0
- intentkit/skills/github/__init__.py +54 -0
- intentkit/skills/github/base.py +21 -0
- intentkit/skills/github/github.jpg +0 -0
- intentkit/skills/github/github_search.py +183 -0
- intentkit/skills/github/schema.json +59 -0
- intentkit/skills/heurist/__init__.py +143 -0
- intentkit/skills/heurist/base.py +26 -0
- intentkit/skills/heurist/heurist.png +0 -0
- intentkit/skills/heurist/image_generation_animagine_xl.py +162 -0
- intentkit/skills/heurist/image_generation_arthemy_comics.py +162 -0
- intentkit/skills/heurist/image_generation_arthemy_real.py +162 -0
- intentkit/skills/heurist/image_generation_braindance.py +162 -0
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +162 -0
- intentkit/skills/heurist/image_generation_flux_1_dev.py +162 -0
- intentkit/skills/heurist/image_generation_sdxl.py +161 -0
- intentkit/skills/heurist/schema.json +196 -0
- intentkit/skills/lifi/README.md +294 -0
- intentkit/skills/lifi/__init__.py +141 -0
- intentkit/skills/lifi/base.py +21 -0
- intentkit/skills/lifi/lifi.png +0 -0
- intentkit/skills/lifi/schema.json +89 -0
- intentkit/skills/lifi/token_execute.py +472 -0
- intentkit/skills/lifi/token_quote.py +190 -0
- intentkit/skills/lifi/utils.py +656 -0
- intentkit/skills/moralis/README.md +490 -0
- intentkit/skills/moralis/__init__.py +110 -0
- intentkit/skills/moralis/api.py +281 -0
- intentkit/skills/moralis/base.py +55 -0
- intentkit/skills/moralis/fetch_chain_portfolio.py +191 -0
- intentkit/skills/moralis/fetch_nft_portfolio.py +284 -0
- intentkit/skills/moralis/fetch_solana_portfolio.py +331 -0
- intentkit/skills/moralis/fetch_wallet_portfolio.py +301 -0
- intentkit/skills/moralis/moralis.png +0 -0
- intentkit/skills/moralis/schema.json +156 -0
- intentkit/skills/moralis/tests/__init__.py +0 -0
- intentkit/skills/moralis/tests/test_wallet.py +511 -0
- intentkit/skills/nation/__init__.py +62 -0
- intentkit/skills/nation/base.py +31 -0
- intentkit/skills/nation/nation.png +0 -0
- intentkit/skills/nation/nft_check.py +106 -0
- intentkit/skills/nation/schema.json +58 -0
- intentkit/skills/openai/__init__.py +107 -0
- intentkit/skills/openai/base.py +32 -0
- intentkit/skills/openai/dalle_image_generation.py +128 -0
- intentkit/skills/openai/gpt_image_generation.py +152 -0
- intentkit/skills/openai/gpt_image_to_image.py +186 -0
- intentkit/skills/openai/image_to_text.py +126 -0
- intentkit/skills/openai/openai.png +0 -0
- intentkit/skills/openai/schema.json +139 -0
- intentkit/skills/portfolio/README.md +55 -0
- intentkit/skills/portfolio/__init__.py +151 -0
- intentkit/skills/portfolio/base.py +107 -0
- intentkit/skills/portfolio/constants.py +9 -0
- intentkit/skills/portfolio/moralis.png +0 -0
- intentkit/skills/portfolio/schema.json +237 -0
- intentkit/skills/portfolio/token_balances.py +155 -0
- intentkit/skills/portfolio/wallet_approvals.py +102 -0
- intentkit/skills/portfolio/wallet_defi_positions.py +80 -0
- intentkit/skills/portfolio/wallet_history.py +155 -0
- intentkit/skills/portfolio/wallet_net_worth.py +112 -0
- intentkit/skills/portfolio/wallet_nfts.py +139 -0
- intentkit/skills/portfolio/wallet_profitability.py +101 -0
- intentkit/skills/portfolio/wallet_profitability_summary.py +91 -0
- intentkit/skills/portfolio/wallet_stats.py +79 -0
- intentkit/skills/portfolio/wallet_swaps.py +147 -0
- intentkit/skills/skills.toml +103 -0
- intentkit/skills/slack/__init__.py +98 -0
- intentkit/skills/slack/base.py +55 -0
- intentkit/skills/slack/get_channel.py +109 -0
- intentkit/skills/slack/get_message.py +136 -0
- intentkit/skills/slack/schedule_message.py +92 -0
- intentkit/skills/slack/schema.json +135 -0
- intentkit/skills/slack/send_message.py +81 -0
- intentkit/skills/slack/slack.jpg +0 -0
- intentkit/skills/system/__init__.py +90 -0
- intentkit/skills/system/base.py +22 -0
- intentkit/skills/system/read_agent_api_key.py +87 -0
- intentkit/skills/system/regenerate_agent_api_key.py +77 -0
- intentkit/skills/system/schema.json +53 -0
- intentkit/skills/system/system.svg +76 -0
- intentkit/skills/tavily/README.md +86 -0
- intentkit/skills/tavily/__init__.py +91 -0
- intentkit/skills/tavily/base.py +27 -0
- intentkit/skills/tavily/schema.json +119 -0
- intentkit/skills/tavily/tavily.jpg +0 -0
- intentkit/skills/tavily/tavily_extract.py +147 -0
- intentkit/skills/tavily/tavily_search.py +139 -0
- intentkit/skills/token/README.md +89 -0
- intentkit/skills/token/__init__.py +107 -0
- intentkit/skills/token/base.py +154 -0
- intentkit/skills/token/constants.py +9 -0
- intentkit/skills/token/erc20_transfers.py +145 -0
- intentkit/skills/token/moralis.png +0 -0
- intentkit/skills/token/schema.json +141 -0
- intentkit/skills/token/token_analytics.py +81 -0
- intentkit/skills/token/token_price.py +132 -0
- intentkit/skills/token/token_search.py +121 -0
- intentkit/skills/twitter/__init__.py +146 -0
- intentkit/skills/twitter/base.py +68 -0
- intentkit/skills/twitter/follow_user.py +69 -0
- intentkit/skills/twitter/get_mentions.py +124 -0
- intentkit/skills/twitter/get_timeline.py +111 -0
- intentkit/skills/twitter/get_user_by_username.py +84 -0
- intentkit/skills/twitter/get_user_tweets.py +123 -0
- intentkit/skills/twitter/like_tweet.py +65 -0
- intentkit/skills/twitter/post_tweet.py +90 -0
- intentkit/skills/twitter/reply_tweet.py +98 -0
- intentkit/skills/twitter/retweet.py +76 -0
- intentkit/skills/twitter/schema.json +258 -0
- intentkit/skills/twitter/search_tweets.py +115 -0
- intentkit/skills/twitter/twitter.png +0 -0
- intentkit/skills/unrealspeech/__init__.py +55 -0
- intentkit/skills/unrealspeech/base.py +21 -0
- intentkit/skills/unrealspeech/schema.json +100 -0
- intentkit/skills/unrealspeech/text_to_speech.py +177 -0
- intentkit/skills/unrealspeech/unrealspeech.jpg +0 -0
- intentkit/skills/venice_audio/__init__.py +106 -0
- intentkit/skills/venice_audio/base.py +119 -0
- intentkit/skills/venice_audio/input.py +41 -0
- intentkit/skills/venice_audio/schema.json +152 -0
- intentkit/skills/venice_audio/venice_audio.py +240 -0
- intentkit/skills/venice_audio/venice_logo.jpg +0 -0
- intentkit/skills/venice_image/README.md +119 -0
- intentkit/skills/venice_image/__init__.py +154 -0
- intentkit/skills/venice_image/api.py +138 -0
- intentkit/skills/venice_image/base.py +188 -0
- intentkit/skills/venice_image/config.py +35 -0
- intentkit/skills/venice_image/image_enhance/README.md +119 -0
- intentkit/skills/venice_image/image_enhance/__init__.py +0 -0
- intentkit/skills/venice_image/image_enhance/image_enhance.py +80 -0
- intentkit/skills/venice_image/image_enhance/image_enhance_base.py +23 -0
- intentkit/skills/venice_image/image_enhance/image_enhance_input.py +40 -0
- intentkit/skills/venice_image/image_generation/README.md +144 -0
- intentkit/skills/venice_image/image_generation/__init__.py +0 -0
- intentkit/skills/venice_image/image_generation/image_generation_base.py +117 -0
- intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -0
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -0
- intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -0
- intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -0
- intentkit/skills/venice_image/image_upscale/README.md +111 -0
- intentkit/skills/venice_image/image_upscale/__init__.py +0 -0
- intentkit/skills/venice_image/image_upscale/image_upscale.py +90 -0
- intentkit/skills/venice_image/image_upscale/image_upscale_base.py +23 -0
- intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -0
- intentkit/skills/venice_image/image_vision/README.md +112 -0
- intentkit/skills/venice_image/image_vision/__init__.py +0 -0
- intentkit/skills/venice_image/image_vision/image_vision.py +100 -0
- intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -0
- intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -0
- intentkit/skills/venice_image/schema.json +267 -0
- intentkit/skills/venice_image/utils.py +78 -0
- intentkit/skills/venice_image/venice_image.jpg +0 -0
- intentkit/skills/web_scraper/README.md +82 -0
- intentkit/skills/web_scraper/__init__.py +92 -0
- intentkit/skills/web_scraper/base.py +21 -0
- intentkit/skills/web_scraper/langchain.png +0 -0
- intentkit/skills/web_scraper/schema.json +115 -0
- intentkit/skills/web_scraper/scrape_and_index.py +327 -0
- intentkit/utils/__init__.py +1 -0
- intentkit/utils/chain.py +436 -0
- intentkit/utils/error.py +134 -0
- intentkit/utils/logging.py +70 -0
- intentkit/utils/middleware.py +61 -0
- intentkit/utils/random.py +16 -0
- intentkit/utils/s3.py +267 -0
- intentkit/utils/slack_alert.py +79 -0
- intentkit/utils/tx.py +37 -0
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/METADATA +1 -1
- intentkit-0.5.2.dist-info/RECORD +365 -0
- intentkit-0.5.0.dist-info/RECORD +0 -4
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/WHEEL +0 -0
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Type
|
|
3
|
+
|
|
4
|
+
from langchain_core.runnables import RunnableConfig
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from intentkit.skills.token.base import TokenBaseTool
|
|
8
|
+
from intentkit.skills.token.constants import DEFAULT_CHAIN
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TokenAnalyticsInput(BaseModel):
|
|
14
|
+
"""Input for token analytics tool."""
|
|
15
|
+
|
|
16
|
+
address: str = Field(description="The token address to get analytics for.")
|
|
17
|
+
chain: str = Field(
|
|
18
|
+
description="The chain to query (e.g., 'eth', 'bsc', 'polygon').",
|
|
19
|
+
default=DEFAULT_CHAIN,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TokenAnalytics(TokenBaseTool):
|
|
24
|
+
"""Tool for retrieving token analytics using Moralis.
|
|
25
|
+
|
|
26
|
+
This tool uses Moralis' API to fetch analytics for a token by token address,
|
|
27
|
+
including trading volume, buyer/seller data, and liquidity information.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name: str = "token_analytics"
|
|
31
|
+
description: str = (
|
|
32
|
+
"Get analytics for a token by token address. "
|
|
33
|
+
"Returns trading volumes, number of buyers/sellers, and liquidity information over various time periods."
|
|
34
|
+
)
|
|
35
|
+
args_schema: Type[BaseModel] = TokenAnalyticsInput
|
|
36
|
+
|
|
37
|
+
async def _arun(
|
|
38
|
+
self,
|
|
39
|
+
address: str,
|
|
40
|
+
chain: str = DEFAULT_CHAIN,
|
|
41
|
+
config: RunnableConfig = None,
|
|
42
|
+
**kwargs,
|
|
43
|
+
) -> Dict[str, Any]:
|
|
44
|
+
"""Fetch token analytics from Moralis.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
address: The token address
|
|
48
|
+
chain: The blockchain to query
|
|
49
|
+
config: The configuration for the tool call
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Dict containing token analytics data
|
|
53
|
+
"""
|
|
54
|
+
context = self.context_from_config(config)
|
|
55
|
+
if context is None:
|
|
56
|
+
logger.error("Context is None, cannot retrieve API key")
|
|
57
|
+
return {
|
|
58
|
+
"error": "Cannot retrieve API key. Please check agent configuration."
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Get the API key
|
|
62
|
+
api_key = self.get_api_key(context)
|
|
63
|
+
|
|
64
|
+
if not api_key:
|
|
65
|
+
logger.error("No Moralis API key available")
|
|
66
|
+
return {"error": "No Moralis API key provided in the configuration."}
|
|
67
|
+
|
|
68
|
+
# Build query parameters
|
|
69
|
+
params = {"chain": chain}
|
|
70
|
+
|
|
71
|
+
# Call Moralis API
|
|
72
|
+
try:
|
|
73
|
+
endpoint = f"/tokens/{address}/analytics"
|
|
74
|
+
return await self._make_request(
|
|
75
|
+
method="GET", endpoint=endpoint, api_key=api_key, params=params
|
|
76
|
+
)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"Error fetching token analytics: {e}")
|
|
79
|
+
return {
|
|
80
|
+
"error": f"An error occurred while fetching token analytics: {str(e)}. Please try again later."
|
|
81
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Optional, Type
|
|
3
|
+
|
|
4
|
+
from langchain_core.runnables import RunnableConfig
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from intentkit.skills.token.base import TokenBaseTool
|
|
8
|
+
from intentkit.skills.token.constants import DEFAULT_CHAIN
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TokenPriceInput(BaseModel):
|
|
14
|
+
"""Input for token price tool."""
|
|
15
|
+
|
|
16
|
+
address: str = Field(
|
|
17
|
+
description="The address of the token contract to get price for."
|
|
18
|
+
)
|
|
19
|
+
chain: str = Field(
|
|
20
|
+
description="The chain to query (e.g., 'eth', 'bsc', 'polygon').",
|
|
21
|
+
default=DEFAULT_CHAIN,
|
|
22
|
+
)
|
|
23
|
+
include: Optional[str] = Field(
|
|
24
|
+
description="If the result should contain the 24hr percent change (use 'percent_change').",
|
|
25
|
+
default=None,
|
|
26
|
+
)
|
|
27
|
+
exchange: Optional[str] = Field(
|
|
28
|
+
description="The factory name or address of the token exchange.",
|
|
29
|
+
default=None,
|
|
30
|
+
)
|
|
31
|
+
to_block: Optional[int] = Field(
|
|
32
|
+
description="The block number from which the token price should be checked.",
|
|
33
|
+
default=None,
|
|
34
|
+
)
|
|
35
|
+
max_token_inactivity: Optional[int] = Field(
|
|
36
|
+
description="Exclude tokens inactive for more than the given amount of days.",
|
|
37
|
+
default=None,
|
|
38
|
+
)
|
|
39
|
+
min_pair_side_liquidity_usd: Optional[int] = Field(
|
|
40
|
+
description="Exclude tokens with liquidity less than the specified amount in USD.",
|
|
41
|
+
default=None,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TokenPrice(TokenBaseTool):
|
|
46
|
+
"""Tool for retrieving ERC20 token prices using Moralis.
|
|
47
|
+
|
|
48
|
+
This tool uses Moralis' API to fetch the token price denominated in the blockchain's native token
|
|
49
|
+
and USD for a given token contract address.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str = "token_price"
|
|
53
|
+
description: str = (
|
|
54
|
+
"Get the token price denominated in the blockchain's native token and USD for a given token contract address. "
|
|
55
|
+
"Returns price, token information and exchange data."
|
|
56
|
+
)
|
|
57
|
+
args_schema: Type[BaseModel] = TokenPriceInput
|
|
58
|
+
|
|
59
|
+
async def _arun(
|
|
60
|
+
self,
|
|
61
|
+
address: str,
|
|
62
|
+
chain: str = DEFAULT_CHAIN,
|
|
63
|
+
include: Optional[str] = None,
|
|
64
|
+
exchange: Optional[str] = None,
|
|
65
|
+
to_block: Optional[int] = None,
|
|
66
|
+
max_token_inactivity: Optional[int] = None,
|
|
67
|
+
min_pair_side_liquidity_usd: Optional[int] = None,
|
|
68
|
+
config: RunnableConfig = None,
|
|
69
|
+
**kwargs,
|
|
70
|
+
) -> Dict[str, Any]:
|
|
71
|
+
"""Fetch token price from Moralis.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
address: The token contract address
|
|
75
|
+
chain: The blockchain to query
|
|
76
|
+
include: Include 24hr percent change
|
|
77
|
+
exchange: The token exchange factory name or address
|
|
78
|
+
to_block: Block number to check price from
|
|
79
|
+
max_token_inactivity: Max days of inactivity to exclude tokens
|
|
80
|
+
min_pair_side_liquidity_usd: Min liquidity in USD to include
|
|
81
|
+
config: The configuration for the tool call
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dict containing token price data
|
|
85
|
+
"""
|
|
86
|
+
# Extract context from config
|
|
87
|
+
context = self.context_from_config(config)
|
|
88
|
+
|
|
89
|
+
if context is None:
|
|
90
|
+
logger.error("Context is None, cannot retrieve API key")
|
|
91
|
+
return {
|
|
92
|
+
"error": "Cannot retrieve API key. Please check agent configuration."
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Get the API key
|
|
96
|
+
api_key = self.get_api_key(context)
|
|
97
|
+
|
|
98
|
+
if not api_key:
|
|
99
|
+
logger.error("No Moralis API key available")
|
|
100
|
+
return {"error": "No Moralis API key provided in the configuration."}
|
|
101
|
+
|
|
102
|
+
# Build query parameters
|
|
103
|
+
params = {"chain": chain}
|
|
104
|
+
|
|
105
|
+
# Add optional parameters if they exist
|
|
106
|
+
if include:
|
|
107
|
+
params["include"] = include
|
|
108
|
+
if exchange:
|
|
109
|
+
params["exchange"] = exchange
|
|
110
|
+
if to_block:
|
|
111
|
+
params["to_block"] = to_block
|
|
112
|
+
if max_token_inactivity:
|
|
113
|
+
params["max_token_inactivity"] = max_token_inactivity
|
|
114
|
+
if min_pair_side_liquidity_usd:
|
|
115
|
+
params["min_pair_side_liquidity_usd"] = min_pair_side_liquidity_usd
|
|
116
|
+
|
|
117
|
+
# Call Moralis API
|
|
118
|
+
try:
|
|
119
|
+
endpoint = f"/erc20/{address}/price"
|
|
120
|
+
response = await self._make_request(
|
|
121
|
+
method="GET", endpoint=endpoint, api_key=api_key, params=params
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if "error" in response:
|
|
125
|
+
logger.error(f"API returned error: {response.get('error')}")
|
|
126
|
+
|
|
127
|
+
return response
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Error fetching token price: {e}")
|
|
130
|
+
return {
|
|
131
|
+
"error": f"An error occurred while fetching token price: {str(e)}. Please try again later."
|
|
132
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List, Optional, Type
|
|
3
|
+
|
|
4
|
+
from langchain_core.runnables import RunnableConfig
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from intentkit.skills.token.base import TokenBaseTool
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TokenSearchInput(BaseModel):
|
|
13
|
+
"""Input for token search tool."""
|
|
14
|
+
|
|
15
|
+
query: str = Field(
|
|
16
|
+
description="Search query - can be token address, token name or token symbol."
|
|
17
|
+
)
|
|
18
|
+
chains: Optional[List[str]] = Field(
|
|
19
|
+
description="The chain(s) to query (e.g., 'eth', 'bsc', 'polygon').",
|
|
20
|
+
default=None,
|
|
21
|
+
)
|
|
22
|
+
limit: Optional[int] = Field(
|
|
23
|
+
description="The desired page size of the result.",
|
|
24
|
+
default=None,
|
|
25
|
+
)
|
|
26
|
+
is_verified_contract: Optional[bool] = Field(
|
|
27
|
+
description="Whether the contract is verified.",
|
|
28
|
+
default=None,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TokenSearch(TokenBaseTool):
|
|
33
|
+
"""Tool for searching tokens using Moralis.
|
|
34
|
+
|
|
35
|
+
This tool uses Moralis' premium API to search for tokens based on contract address,
|
|
36
|
+
token name or token symbol.
|
|
37
|
+
|
|
38
|
+
NOTE: To use this API, you will need an API key associated with a Moralis account
|
|
39
|
+
on the Business plan or a custom Enterprise plan.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
name: str = "token_search"
|
|
43
|
+
description: str = (
|
|
44
|
+
"Search for tokens based on contract address, token name or token symbol. "
|
|
45
|
+
"Returns token information including price, market cap, and security information. "
|
|
46
|
+
"NOTE: This is a premium endpoint that requires a Moralis Business plan."
|
|
47
|
+
)
|
|
48
|
+
args_schema: Type[BaseModel] = TokenSearchInput
|
|
49
|
+
|
|
50
|
+
async def _arun(
|
|
51
|
+
self,
|
|
52
|
+
query: str,
|
|
53
|
+
chains: Optional[List[str]] = None,
|
|
54
|
+
limit: Optional[int] = None,
|
|
55
|
+
is_verified_contract: Optional[bool] = None,
|
|
56
|
+
config: RunnableConfig = None,
|
|
57
|
+
**kwargs,
|
|
58
|
+
) -> Dict[str, Any]:
|
|
59
|
+
"""Search for tokens using Moralis.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
query: Search query (address, name, or symbol)
|
|
63
|
+
chains: The blockchains to query
|
|
64
|
+
limit: Number of results
|
|
65
|
+
is_verified_contract: Filter for verified contracts
|
|
66
|
+
config: The configuration for the tool call
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dict containing token search results
|
|
70
|
+
"""
|
|
71
|
+
# Extract context from config
|
|
72
|
+
context = self.context_from_config(config)
|
|
73
|
+
if context is None:
|
|
74
|
+
logger.error("Context is None, cannot retrieve API key")
|
|
75
|
+
return {
|
|
76
|
+
"error": "Cannot retrieve API key. Please check agent configuration."
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Get the API key
|
|
80
|
+
api_key = self.get_api_key(context)
|
|
81
|
+
|
|
82
|
+
if not api_key:
|
|
83
|
+
logger.error("No Moralis API key available")
|
|
84
|
+
return {"error": "No Moralis API key provided in the configuration."}
|
|
85
|
+
|
|
86
|
+
# Build query parameters
|
|
87
|
+
params = {"query": query}
|
|
88
|
+
|
|
89
|
+
# Add optional parameters if they exist
|
|
90
|
+
if chains:
|
|
91
|
+
params["chains"] = ",".join(chains)
|
|
92
|
+
if limit:
|
|
93
|
+
params["limit"] = limit
|
|
94
|
+
if is_verified_contract is not None:
|
|
95
|
+
params["isVerifiedContract"] = is_verified_contract
|
|
96
|
+
|
|
97
|
+
# Call Moralis API
|
|
98
|
+
try:
|
|
99
|
+
endpoint = "/tokens/search"
|
|
100
|
+
result = await self._make_request(
|
|
101
|
+
method="GET", endpoint=endpoint, api_key=api_key, params=params
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Add premium notice if there's an error that might be related to plan limits
|
|
105
|
+
if "error" in result and "403" in str(result.get("error", "")):
|
|
106
|
+
logger.error("Received 403 error - likely a plan limitation")
|
|
107
|
+
result["notice"] = (
|
|
108
|
+
"This API requires a Moralis Business plan or Enterprise plan. "
|
|
109
|
+
"Please ensure your API key is associated with the appropriate plan."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return result
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(f"Error searching for tokens: {e}")
|
|
115
|
+
return {
|
|
116
|
+
"error": f"An error occurred while searching for tokens: {str(e)}. Please try again later.",
|
|
117
|
+
"notice": (
|
|
118
|
+
"This API requires a Moralis Business plan or Enterprise plan. "
|
|
119
|
+
"Please ensure your API key is associated with the appropriate plan."
|
|
120
|
+
),
|
|
121
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Twitter skills."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
from intentkit.abstracts.skill import SkillStoreABC
|
|
7
|
+
from intentkit.clients import TwitterClientConfig
|
|
8
|
+
from intentkit.skills.base import SkillConfig, SkillState
|
|
9
|
+
from intentkit.skills.twitter.base import TwitterBaseTool
|
|
10
|
+
from intentkit.skills.twitter.follow_user import TwitterFollowUser
|
|
11
|
+
from intentkit.skills.twitter.get_mentions import TwitterGetMentions
|
|
12
|
+
from intentkit.skills.twitter.get_timeline import TwitterGetTimeline
|
|
13
|
+
from intentkit.skills.twitter.get_user_by_username import TwitterGetUserByUsername
|
|
14
|
+
from intentkit.skills.twitter.get_user_tweets import TwitterGetUserTweets
|
|
15
|
+
from intentkit.skills.twitter.like_tweet import TwitterLikeTweet
|
|
16
|
+
from intentkit.skills.twitter.post_tweet import TwitterPostTweet
|
|
17
|
+
from intentkit.skills.twitter.reply_tweet import TwitterReplyTweet
|
|
18
|
+
from intentkit.skills.twitter.retweet import TwitterRetweet
|
|
19
|
+
from intentkit.skills.twitter.search_tweets import TwitterSearchTweets
|
|
20
|
+
|
|
21
|
+
# we cache skills in system level, because they are stateless
|
|
22
|
+
_cache: dict[str, TwitterBaseTool] = {}
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SkillStates(TypedDict):
|
|
28
|
+
get_mentions: SkillState
|
|
29
|
+
post_tweet: SkillState
|
|
30
|
+
reply_tweet: SkillState
|
|
31
|
+
get_timeline: SkillState
|
|
32
|
+
get_user_by_username: SkillState
|
|
33
|
+
get_user_tweets: SkillState
|
|
34
|
+
follow_user: SkillState
|
|
35
|
+
like_tweet: SkillState
|
|
36
|
+
retweet: SkillState
|
|
37
|
+
search_tweets: SkillState
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Config(SkillConfig, TwitterClientConfig):
|
|
41
|
+
"""Configuration for Twitter skills."""
|
|
42
|
+
|
|
43
|
+
states: SkillStates
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def get_skills(
|
|
47
|
+
config: "Config",
|
|
48
|
+
is_private: bool,
|
|
49
|
+
store: SkillStoreABC,
|
|
50
|
+
**_,
|
|
51
|
+
) -> list[TwitterBaseTool]:
|
|
52
|
+
"""Get all Twitter skills."""
|
|
53
|
+
available_skills = []
|
|
54
|
+
|
|
55
|
+
# Include skills based on their state
|
|
56
|
+
for skill_name, state in config["states"].items():
|
|
57
|
+
if state == "disabled":
|
|
58
|
+
continue
|
|
59
|
+
elif state == "public" or (state == "private" and is_private):
|
|
60
|
+
available_skills.append(skill_name)
|
|
61
|
+
|
|
62
|
+
# Get each skill using the cached getter
|
|
63
|
+
result = []
|
|
64
|
+
for name in available_skills:
|
|
65
|
+
skill = get_twitter_skill(name, store)
|
|
66
|
+
if skill:
|
|
67
|
+
result.append(skill)
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_twitter_skill(
|
|
72
|
+
name: str,
|
|
73
|
+
store: SkillStoreABC,
|
|
74
|
+
) -> TwitterBaseTool:
|
|
75
|
+
"""Get a Twitter skill by name.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name: The name of the skill to get
|
|
79
|
+
store: The skill store for persisting data
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The requested Twitter skill
|
|
83
|
+
"""
|
|
84
|
+
if name == "get_mentions":
|
|
85
|
+
if name not in _cache:
|
|
86
|
+
_cache[name] = TwitterGetMentions(
|
|
87
|
+
skill_store=store,
|
|
88
|
+
)
|
|
89
|
+
return _cache[name]
|
|
90
|
+
elif name == "post_tweet":
|
|
91
|
+
if name not in _cache:
|
|
92
|
+
_cache[name] = TwitterPostTweet(
|
|
93
|
+
skill_store=store,
|
|
94
|
+
)
|
|
95
|
+
return _cache[name]
|
|
96
|
+
elif name == "reply_tweet":
|
|
97
|
+
if name not in _cache:
|
|
98
|
+
_cache[name] = TwitterReplyTweet(
|
|
99
|
+
skill_store=store,
|
|
100
|
+
)
|
|
101
|
+
return _cache[name]
|
|
102
|
+
elif name == "get_timeline":
|
|
103
|
+
if name not in _cache:
|
|
104
|
+
_cache[name] = TwitterGetTimeline(
|
|
105
|
+
skill_store=store,
|
|
106
|
+
)
|
|
107
|
+
return _cache[name]
|
|
108
|
+
elif name == "follow_user":
|
|
109
|
+
if name not in _cache:
|
|
110
|
+
_cache[name] = TwitterFollowUser(
|
|
111
|
+
skill_store=store,
|
|
112
|
+
)
|
|
113
|
+
return _cache[name]
|
|
114
|
+
elif name == "like_tweet":
|
|
115
|
+
if name not in _cache:
|
|
116
|
+
_cache[name] = TwitterLikeTweet(
|
|
117
|
+
skill_store=store,
|
|
118
|
+
)
|
|
119
|
+
return _cache[name]
|
|
120
|
+
elif name == "retweet":
|
|
121
|
+
if name not in _cache:
|
|
122
|
+
_cache[name] = TwitterRetweet(
|
|
123
|
+
skill_store=store,
|
|
124
|
+
)
|
|
125
|
+
return _cache[name]
|
|
126
|
+
elif name == "search_tweets":
|
|
127
|
+
if name not in _cache:
|
|
128
|
+
_cache[name] = TwitterSearchTweets(
|
|
129
|
+
skill_store=store,
|
|
130
|
+
)
|
|
131
|
+
return _cache[name]
|
|
132
|
+
elif name == "get_user_by_username":
|
|
133
|
+
if name not in _cache:
|
|
134
|
+
_cache[name] = TwitterGetUserByUsername(
|
|
135
|
+
skill_store=store,
|
|
136
|
+
)
|
|
137
|
+
return _cache[name]
|
|
138
|
+
elif name == "get_user_tweets":
|
|
139
|
+
if name not in _cache:
|
|
140
|
+
_cache[name] = TwitterGetUserTweets(
|
|
141
|
+
skill_store=store,
|
|
142
|
+
)
|
|
143
|
+
return _cache[name]
|
|
144
|
+
else:
|
|
145
|
+
logger.warning(f"Unknown Twitter skill: {name}")
|
|
146
|
+
return None
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from intentkit.abstracts.exception import RateLimitExceeded
|
|
7
|
+
from intentkit.abstracts.skill import SkillStoreABC
|
|
8
|
+
from intentkit.skills.base import IntentKitSkill
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TwitterBaseTool(IntentKitSkill):
|
|
12
|
+
"""Base class for Twitter tools."""
|
|
13
|
+
|
|
14
|
+
name: str = Field(description="The name of the tool")
|
|
15
|
+
description: str = Field(description="A description of what the tool does")
|
|
16
|
+
args_schema: Type[BaseModel]
|
|
17
|
+
skill_store: SkillStoreABC = Field(
|
|
18
|
+
description="The skill store for persisting data"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def category(self) -> str:
|
|
23
|
+
return "twitter"
|
|
24
|
+
|
|
25
|
+
async def check_rate_limit(
|
|
26
|
+
self, agent_id: str, max_requests: int = 1, interval: int = 15
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Check if the rate limit has been exceeded.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
agent_id: The ID of the agent.
|
|
32
|
+
max_requests: Maximum number of requests allowed within the rate limit window.
|
|
33
|
+
interval: Time interval in minutes for the rate limit window.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
RateLimitExceeded: If the rate limit has been exceeded.
|
|
37
|
+
"""
|
|
38
|
+
rate_limit = await self.skill_store.get_agent_skill_data(
|
|
39
|
+
agent_id, self.name, "rate_limit"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
current_time = datetime.now(tz=timezone.utc)
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
rate_limit
|
|
46
|
+
and rate_limit.get("reset_time")
|
|
47
|
+
and rate_limit["count"] is not None
|
|
48
|
+
and datetime.fromisoformat(rate_limit["reset_time"]) > current_time
|
|
49
|
+
):
|
|
50
|
+
if rate_limit["count"] >= max_requests:
|
|
51
|
+
raise RateLimitExceeded("Rate limit exceeded")
|
|
52
|
+
|
|
53
|
+
rate_limit["count"] += 1
|
|
54
|
+
await self.skill_store.save_agent_skill_data(
|
|
55
|
+
agent_id, self.name, "rate_limit", rate_limit
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# If no rate limit exists or it has expired, create a new one
|
|
61
|
+
new_rate_limit = {
|
|
62
|
+
"count": 1,
|
|
63
|
+
"reset_time": (current_time + timedelta(minutes=interval)).isoformat(),
|
|
64
|
+
}
|
|
65
|
+
await self.skill_store.save_agent_skill_data(
|
|
66
|
+
agent_id, self.name, "rate_limit", new_rate_limit
|
|
67
|
+
)
|
|
68
|
+
return
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from langchain_core.runnables import RunnableConfig
|
|
5
|
+
from langchain_core.tools import ToolException
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from intentkit.clients import get_twitter_client
|
|
9
|
+
from intentkit.skills.twitter.base import TwitterBaseTool
|
|
10
|
+
|
|
11
|
+
NAME = "twitter_follow_user"
|
|
12
|
+
PROMPT = (
|
|
13
|
+
"Follow a Twitter user, if you don't know the user ID, "
|
|
14
|
+
"use twitter_get_user_by_username tool to get it."
|
|
15
|
+
)
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TwitterFollowUserInput(BaseModel):
|
|
20
|
+
"""Input for TwitterFollowUser tool."""
|
|
21
|
+
|
|
22
|
+
user_id: str = Field(description="The ID of the user to follow")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TwitterFollowUser(TwitterBaseTool):
|
|
26
|
+
"""Tool for following a Twitter user.
|
|
27
|
+
|
|
28
|
+
This tool uses the Twitter API v2 to follow a user on Twitter.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
name: The name of the tool.
|
|
32
|
+
description: A description of what the tool does.
|
|
33
|
+
args_schema: The schema for the tool's input arguments.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
name: str = NAME
|
|
37
|
+
description: str = PROMPT
|
|
38
|
+
args_schema: Type[BaseModel] = TwitterFollowUserInput
|
|
39
|
+
|
|
40
|
+
async def _arun(self, user_id: str, config: RunnableConfig, **kwargs) -> bool:
|
|
41
|
+
try:
|
|
42
|
+
context = self.context_from_config(config)
|
|
43
|
+
twitter = get_twitter_client(
|
|
44
|
+
agent_id=context.agent.id,
|
|
45
|
+
skill_store=self.skill_store,
|
|
46
|
+
config=context.config,
|
|
47
|
+
)
|
|
48
|
+
client = await twitter.get_client()
|
|
49
|
+
|
|
50
|
+
# Check rate limit only when not using OAuth
|
|
51
|
+
if not twitter.use_key:
|
|
52
|
+
await self.check_rate_limit(
|
|
53
|
+
context.agent.id, max_requests=5, interval=15
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Follow the user using tweepy client
|
|
57
|
+
response = await client.follow_user(
|
|
58
|
+
target_user_id=user_id, user_auth=twitter.use_key
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if "data" in response and response["data"].get("following"):
|
|
62
|
+
return response
|
|
63
|
+
else:
|
|
64
|
+
logger.error(f"Error following user: {str(response)}")
|
|
65
|
+
raise ToolException("Failed to follow user")
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error("Error following user: %s", str(e))
|
|
69
|
+
raise type(e)(f"[agent:{context.agent.id}]: {e}") from e
|