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,130 @@
|
|
|
1
|
+
"""CryptoCompare skills."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
from intentkit.abstracts.skill import SkillStoreABC
|
|
7
|
+
from intentkit.skills.base import SkillConfig, SkillState
|
|
8
|
+
from intentkit.skills.cryptocompare.base import CryptoCompareBaseTool
|
|
9
|
+
from intentkit.skills.cryptocompare.fetch_news import CryptoCompareFetchNews
|
|
10
|
+
from intentkit.skills.cryptocompare.fetch_price import CryptoCompareFetchPrice
|
|
11
|
+
from intentkit.skills.cryptocompare.fetch_top_exchanges import (
|
|
12
|
+
CryptoCompareFetchTopExchanges,
|
|
13
|
+
)
|
|
14
|
+
from intentkit.skills.cryptocompare.fetch_top_market_cap import (
|
|
15
|
+
CryptoCompareFetchTopMarketCap,
|
|
16
|
+
)
|
|
17
|
+
from intentkit.skills.cryptocompare.fetch_top_volume import CryptoCompareFetchTopVolume
|
|
18
|
+
from intentkit.skills.cryptocompare.fetch_trading_signals import (
|
|
19
|
+
CryptoCompareFetchTradingSignals,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Cache skills at the system level, because they are stateless
|
|
23
|
+
_cache: dict[str, CryptoCompareBaseTool] = {}
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SkillStates(TypedDict):
|
|
29
|
+
fetch_news: SkillState
|
|
30
|
+
fetch_price: SkillState
|
|
31
|
+
fetch_trading_signals: SkillState
|
|
32
|
+
fetch_top_market_cap: SkillState
|
|
33
|
+
fetch_top_exchanges: SkillState
|
|
34
|
+
fetch_top_volume: SkillState
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Config(SkillConfig):
|
|
38
|
+
"""Configuration for CryptoCompare skills."""
|
|
39
|
+
|
|
40
|
+
states: SkillStates
|
|
41
|
+
api_key: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def get_skills(
|
|
45
|
+
config: "Config",
|
|
46
|
+
is_private: bool,
|
|
47
|
+
store: SkillStoreABC,
|
|
48
|
+
**_,
|
|
49
|
+
) -> list[CryptoCompareBaseTool]:
|
|
50
|
+
"""Get all CryptoCompare skills.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
config: The configuration for CryptoCompare skills.
|
|
54
|
+
is_private: Whether to include private skills.
|
|
55
|
+
store: The skill store for persisting data.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A list of CryptoCompare skills.
|
|
59
|
+
"""
|
|
60
|
+
available_skills = []
|
|
61
|
+
|
|
62
|
+
# Include skills based on their state
|
|
63
|
+
for skill_name, state in config["states"].items():
|
|
64
|
+
if state == "disabled":
|
|
65
|
+
continue
|
|
66
|
+
elif state == "public" or (state == "private" and is_private):
|
|
67
|
+
available_skills.append(skill_name)
|
|
68
|
+
|
|
69
|
+
# Get each skill using the cached getter
|
|
70
|
+
result = []
|
|
71
|
+
for name in available_skills:
|
|
72
|
+
skill = get_cryptocompare_skill(name, store)
|
|
73
|
+
if skill:
|
|
74
|
+
result.append(skill)
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_cryptocompare_skill(
|
|
79
|
+
name: str,
|
|
80
|
+
store: SkillStoreABC,
|
|
81
|
+
) -> CryptoCompareBaseTool:
|
|
82
|
+
"""Get a CryptoCompare skill by name.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
name: The name of the skill to get
|
|
86
|
+
store: The skill store for persisting data
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The requested CryptoCompare skill
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
if name == "fetch_news":
|
|
93
|
+
if name not in _cache:
|
|
94
|
+
_cache[name] = CryptoCompareFetchNews(
|
|
95
|
+
skill_store=store,
|
|
96
|
+
)
|
|
97
|
+
return _cache[name]
|
|
98
|
+
elif name == "fetch_price":
|
|
99
|
+
if name not in _cache:
|
|
100
|
+
_cache[name] = CryptoCompareFetchPrice(
|
|
101
|
+
skill_store=store,
|
|
102
|
+
)
|
|
103
|
+
return _cache[name]
|
|
104
|
+
elif name == "fetch_trading_signals":
|
|
105
|
+
if name not in _cache:
|
|
106
|
+
_cache[name] = CryptoCompareFetchTradingSignals(
|
|
107
|
+
skill_store=store,
|
|
108
|
+
)
|
|
109
|
+
return _cache[name]
|
|
110
|
+
elif name == "fetch_top_market_cap":
|
|
111
|
+
if name not in _cache:
|
|
112
|
+
_cache[name] = CryptoCompareFetchTopMarketCap(
|
|
113
|
+
skill_store=store,
|
|
114
|
+
)
|
|
115
|
+
return _cache[name]
|
|
116
|
+
elif name == "fetch_top_exchanges":
|
|
117
|
+
if name not in _cache:
|
|
118
|
+
_cache[name] = CryptoCompareFetchTopExchanges(
|
|
119
|
+
skill_store=store,
|
|
120
|
+
)
|
|
121
|
+
return _cache[name]
|
|
122
|
+
elif name == "fetch_top_volume":
|
|
123
|
+
if name not in _cache:
|
|
124
|
+
_cache[name] = CryptoCompareFetchTopVolume(
|
|
125
|
+
skill_store=store,
|
|
126
|
+
)
|
|
127
|
+
return _cache[name]
|
|
128
|
+
else:
|
|
129
|
+
logger.warning(f"Unknown CryptoCompare skill: {name}")
|
|
130
|
+
return None
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""CryptoCompare API implementation and shared schemas."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
CRYPTO_COMPARE_BASE_URL = "https://min-api.cryptocompare.com"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Input Schemas
|
|
13
|
+
class FetchNewsInput(BaseModel):
|
|
14
|
+
"""Input schema for fetching news."""
|
|
15
|
+
|
|
16
|
+
token: str = Field(
|
|
17
|
+
..., description="Token symbol to fetch news for (e.g., BTC, ETH, SOL)"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FetchPriceInput(BaseModel):
|
|
22
|
+
"""Input schema for fetching crypto prices."""
|
|
23
|
+
|
|
24
|
+
from_symbol: str = Field(
|
|
25
|
+
...,
|
|
26
|
+
description="Base cryptocurrency symbol to get prices for (e.g., 'BTC', 'ETH')",
|
|
27
|
+
)
|
|
28
|
+
to_symbols: List[str] = Field(
|
|
29
|
+
...,
|
|
30
|
+
description="List of target currencies (fiat or crypto) (e.g., ['USD', 'EUR', 'JPY'])",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FetchTradingSignalsInput(BaseModel):
|
|
35
|
+
"""Input schema for fetching trading signals."""
|
|
36
|
+
|
|
37
|
+
from_symbol: str = Field(
|
|
38
|
+
...,
|
|
39
|
+
description="Cryptocurrency symbol to fetch trading signals for (e.g., 'BTC')",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FetchTopMarketCapInput(BaseModel):
|
|
44
|
+
"""Input schema for fetching top cryptocurrencies by market cap."""
|
|
45
|
+
|
|
46
|
+
to_symbol: str = Field(
|
|
47
|
+
"USD",
|
|
48
|
+
description="Quote currency for market cap calculation (e.g., 'USD', 'EUR')",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FetchTopExchangesInput(BaseModel):
|
|
53
|
+
"""Input schema for fetching top exchanges for a trading pair."""
|
|
54
|
+
|
|
55
|
+
from_symbol: str = Field(
|
|
56
|
+
..., description="Base cryptocurrency symbol for the trading pair (e.g., 'BTC')"
|
|
57
|
+
)
|
|
58
|
+
to_symbol: str = Field(
|
|
59
|
+
"USD",
|
|
60
|
+
description="Quote currency symbol for the trading pair. Defaults to 'USD'",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class FetchTopVolumeInput(BaseModel):
|
|
65
|
+
"""Input schema for fetching top cryptocurrencies by trading volume."""
|
|
66
|
+
|
|
67
|
+
to_symbol: str = Field(
|
|
68
|
+
"USD", description="Quote currency for volume calculation. Defaults to 'USD'"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# API Functions
|
|
73
|
+
async def fetch_price(api_key: str, from_symbol: str, to_symbols: List[str]) -> dict:
|
|
74
|
+
"""
|
|
75
|
+
Fetch current price for a cryptocurrency in multiple currencies.
|
|
76
|
+
"""
|
|
77
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/price"
|
|
78
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
79
|
+
params = {"fsym": from_symbol.upper(), "tsyms": ",".join(to_symbols)}
|
|
80
|
+
async with httpx.AsyncClient() as client:
|
|
81
|
+
response = await client.get(url, params=params, headers=headers)
|
|
82
|
+
if response.status_code != 200:
|
|
83
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
84
|
+
return response.json()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def fetch_trading_signals(api_key: str, from_symbol: str) -> dict:
|
|
88
|
+
"""
|
|
89
|
+
Fetch the latest trading signals.
|
|
90
|
+
"""
|
|
91
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/tradingsignals/intotheblock/latest"
|
|
92
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
93
|
+
params = {"fsym": from_symbol.upper()}
|
|
94
|
+
async with httpx.AsyncClient() as client:
|
|
95
|
+
response = await client.get(url, params=params, headers=headers)
|
|
96
|
+
if response.status_code != 200:
|
|
97
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
98
|
+
return response.json()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def fetch_top_market_cap(
|
|
102
|
+
api_key: str, limit: int, to_symbol: str = "USD"
|
|
103
|
+
) -> dict:
|
|
104
|
+
"""
|
|
105
|
+
Fetch top cryptocurrencies by market cap.
|
|
106
|
+
"""
|
|
107
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/top/mktcapfull"
|
|
108
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
109
|
+
params = {"limit": limit, "tsym": to_symbol.upper()}
|
|
110
|
+
async with httpx.AsyncClient() as client:
|
|
111
|
+
response = await client.get(url, params=params, headers=headers)
|
|
112
|
+
if response.status_code != 200:
|
|
113
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
114
|
+
return response.json()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def fetch_top_exchanges(
|
|
118
|
+
api_key: str, from_symbol: str, to_symbol: str = "USD"
|
|
119
|
+
) -> dict:
|
|
120
|
+
"""
|
|
121
|
+
Fetch top exchanges for a cryptocurrency pair.
|
|
122
|
+
"""
|
|
123
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/top/exchanges"
|
|
124
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
125
|
+
params = {"fsym": from_symbol.upper(), "tsym": to_symbol.upper()}
|
|
126
|
+
async with httpx.AsyncClient() as client:
|
|
127
|
+
response = await client.get(url, params=params, headers=headers)
|
|
128
|
+
if response.status_code != 200:
|
|
129
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
130
|
+
return response.json()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def fetch_top_volume(api_key: str, limit: int, to_symbol: str = "USD") -> dict:
|
|
134
|
+
"""
|
|
135
|
+
Fetch top cryptocurrencies by total volume.
|
|
136
|
+
"""
|
|
137
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/top/totalvolfull"
|
|
138
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
139
|
+
params = {"limit": limit, "tsym": to_symbol.upper()}
|
|
140
|
+
async with httpx.AsyncClient() as client:
|
|
141
|
+
response = await client.get(url, params=params, headers=headers)
|
|
142
|
+
if response.status_code != 200:
|
|
143
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
144
|
+
return response.json()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
async def fetch_news(api_key: str, token: str, timestamp: int = None) -> dict:
|
|
148
|
+
"""
|
|
149
|
+
Fetch news for a specific token and timestamp.
|
|
150
|
+
"""
|
|
151
|
+
if timestamp is None:
|
|
152
|
+
timestamp = int(time.time())
|
|
153
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/v2/news/?lang=EN&lTs={timestamp}&categories={token}&sign=true"
|
|
154
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
155
|
+
async with httpx.AsyncClient() as client:
|
|
156
|
+
response = await client.get(url, headers=headers)
|
|
157
|
+
if response.status_code != 200:
|
|
158
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
159
|
+
return response.json()
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""Base class for all CryptoCompare tools."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
from typing import Any, Dict, List, Type
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from intentkit.abstracts.exception import RateLimitExceeded
|
|
11
|
+
from intentkit.abstracts.skill import SkillStoreABC
|
|
12
|
+
from intentkit.skills.base import IntentKitSkill
|
|
13
|
+
|
|
14
|
+
CRYPTO_COMPARE_BASE_URL = "https://min-api.cryptocompare.com"
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CryptoCompareBaseTool(IntentKitSkill):
|
|
20
|
+
"""Base class for CryptoCompare tools.
|
|
21
|
+
|
|
22
|
+
This class provides common functionality for all CryptoCompare API tools:
|
|
23
|
+
- Rate limiting
|
|
24
|
+
- API client handling
|
|
25
|
+
- State management through skill_store
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
name: str = Field(description="The name of the tool")
|
|
29
|
+
description: str = Field(description="A description of what the tool does")
|
|
30
|
+
args_schema: Type[BaseModel]
|
|
31
|
+
skill_store: SkillStoreABC = Field(
|
|
32
|
+
description="The skill store for persisting data"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def category(self) -> str:
|
|
37
|
+
return "cryptocompare"
|
|
38
|
+
|
|
39
|
+
async def check_rate_limit(
|
|
40
|
+
self, agent_id: str, max_requests: int = 1, interval: int = 15
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Check if the rate limit has been exceeded.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
agent_id: The ID of the agent.
|
|
46
|
+
max_requests: Maximum number of requests allowed within the rate limit window.
|
|
47
|
+
interval: Time interval in minutes for the rate limit window.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
RateLimitExceeded: If the rate limit has been exceeded.
|
|
51
|
+
"""
|
|
52
|
+
rate_limit = await self.skill_store.get_agent_skill_data(
|
|
53
|
+
agent_id, self.name, "rate_limit"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
current_time = datetime.now(tz=timezone.utc)
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
rate_limit
|
|
60
|
+
and rate_limit.get("reset_time")
|
|
61
|
+
and rate_limit["count"] is not None
|
|
62
|
+
and datetime.fromisoformat(rate_limit["reset_time"]) > current_time
|
|
63
|
+
):
|
|
64
|
+
if rate_limit["count"] >= max_requests:
|
|
65
|
+
raise RateLimitExceeded("Rate limit exceeded")
|
|
66
|
+
|
|
67
|
+
rate_limit["count"] += 1
|
|
68
|
+
await self.skill_store.save_agent_skill_data(
|
|
69
|
+
agent_id, self.name, "rate_limit", rate_limit
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# If no rate limit exists or it has expired, create a new one
|
|
75
|
+
new_rate_limit = {
|
|
76
|
+
"count": 1,
|
|
77
|
+
"reset_time": (current_time + timedelta(minutes=interval)).isoformat(),
|
|
78
|
+
}
|
|
79
|
+
await self.skill_store.save_agent_skill_data(
|
|
80
|
+
agent_id, self.name, "rate_limit", new_rate_limit
|
|
81
|
+
)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
async def fetch_price(
|
|
85
|
+
self, api_key: str, from_symbol: str, to_symbols: List[str]
|
|
86
|
+
) -> dict:
|
|
87
|
+
"""Fetch current price for a cryptocurrency in multiple currencies.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
api_key: The CryptoCompare API key
|
|
91
|
+
from_symbol: Base cryptocurrency symbol to get prices for (e.g., 'BTC', 'ETH')
|
|
92
|
+
to_symbols: List of target currencies (fiat or crypto) (e.g., ['USD', 'EUR', 'JPY'])
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Dict containing the price data
|
|
96
|
+
"""
|
|
97
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/price"
|
|
98
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
99
|
+
|
|
100
|
+
# Ensure from_symbol is a string, not a list
|
|
101
|
+
if isinstance(from_symbol, list):
|
|
102
|
+
from_symbol = from_symbol[0] if from_symbol else ""
|
|
103
|
+
|
|
104
|
+
# Ensure to_symbols is a list
|
|
105
|
+
if not isinstance(to_symbols, list):
|
|
106
|
+
to_symbols = [to_symbols] if to_symbols else ["USD"]
|
|
107
|
+
|
|
108
|
+
params = {
|
|
109
|
+
"fsym": from_symbol.upper(),
|
|
110
|
+
"tsyms": ",".join([s.upper() for s in to_symbols]),
|
|
111
|
+
}
|
|
112
|
+
async with httpx.AsyncClient() as client:
|
|
113
|
+
response = await client.get(url, params=params, headers=headers)
|
|
114
|
+
if response.status_code != 200:
|
|
115
|
+
logger.error(f"API returned status code {response.status_code}")
|
|
116
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
117
|
+
return response.json()
|
|
118
|
+
|
|
119
|
+
async def fetch_trading_signals(self, api_key: str, from_symbol: str) -> dict:
|
|
120
|
+
"""Fetch the latest trading signals.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
api_key: The CryptoCompare API key
|
|
124
|
+
from_symbol: Cryptocurrency symbol to fetch trading signals for (e.g., 'BTC')
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Dict containing the trading signals data
|
|
128
|
+
"""
|
|
129
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/tradingsignals/intotheblock/latest"
|
|
130
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
131
|
+
|
|
132
|
+
# Ensure from_symbol is a string, not a list
|
|
133
|
+
if isinstance(from_symbol, list):
|
|
134
|
+
from_symbol = from_symbol[0] if from_symbol else ""
|
|
135
|
+
|
|
136
|
+
params = {"fsym": from_symbol.upper()}
|
|
137
|
+
async with httpx.AsyncClient() as client:
|
|
138
|
+
response = await client.get(url, params=params, headers=headers)
|
|
139
|
+
if response.status_code != 200:
|
|
140
|
+
logger.error(f"API returned status code {response.status_code}")
|
|
141
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
142
|
+
return response.json()
|
|
143
|
+
|
|
144
|
+
async def fetch_top_market_cap(
|
|
145
|
+
self, api_key: str, limit: int, to_symbol: str = "USD"
|
|
146
|
+
) -> dict:
|
|
147
|
+
"""Fetch top cryptocurrencies by market cap.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
api_key: The CryptoCompare API key
|
|
151
|
+
limit: Number of cryptocurrencies to fetch
|
|
152
|
+
to_symbol: Quote currency for market cap calculation (e.g., 'USD', 'EUR')
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Dict containing the top market cap data
|
|
156
|
+
"""
|
|
157
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/top/mktcapfull"
|
|
158
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
159
|
+
|
|
160
|
+
# Ensure to_symbol is a string, not a list
|
|
161
|
+
if isinstance(to_symbol, list):
|
|
162
|
+
to_symbol = to_symbol[0] if to_symbol else "USD"
|
|
163
|
+
|
|
164
|
+
params = {"limit": limit, "tsym": to_symbol.upper()}
|
|
165
|
+
async with httpx.AsyncClient() as client:
|
|
166
|
+
response = await client.get(url, params=params, headers=headers)
|
|
167
|
+
if response.status_code != 200:
|
|
168
|
+
logger.error(f"API returned status code {response.status_code}")
|
|
169
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
170
|
+
return response.json()
|
|
171
|
+
|
|
172
|
+
async def fetch_top_exchanges(
|
|
173
|
+
self, api_key: str, from_symbol: str, to_symbol: str = "USD"
|
|
174
|
+
) -> dict:
|
|
175
|
+
"""Fetch top exchanges for a cryptocurrency pair.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
api_key: The CryptoCompare API key
|
|
179
|
+
from_symbol: Base cryptocurrency symbol for the trading pair (e.g., 'BTC')
|
|
180
|
+
to_symbol: Quote currency symbol for the trading pair. Defaults to 'USD'
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dict containing the top exchanges data
|
|
184
|
+
"""
|
|
185
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/top/exchanges"
|
|
186
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
187
|
+
|
|
188
|
+
# Ensure from_symbol and to_symbol are strings, not lists
|
|
189
|
+
if isinstance(from_symbol, list):
|
|
190
|
+
from_symbol = from_symbol[0] if from_symbol else ""
|
|
191
|
+
if isinstance(to_symbol, list):
|
|
192
|
+
to_symbol = to_symbol[0] if to_symbol else "USD"
|
|
193
|
+
|
|
194
|
+
params = {"fsym": from_symbol.upper(), "tsym": to_symbol.upper()}
|
|
195
|
+
async with httpx.AsyncClient() as client:
|
|
196
|
+
response = await client.get(url, params=params, headers=headers)
|
|
197
|
+
if response.status_code != 200:
|
|
198
|
+
logger.error(f"API returned status code {response.status_code}")
|
|
199
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
200
|
+
return response.json()
|
|
201
|
+
|
|
202
|
+
async def fetch_top_volume(
|
|
203
|
+
self, api_key: str, limit: int, to_symbol: str = "USD"
|
|
204
|
+
) -> dict:
|
|
205
|
+
"""Fetch top cryptocurrencies by total volume.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
api_key: The CryptoCompare API key
|
|
209
|
+
limit: Number of cryptocurrencies to fetch
|
|
210
|
+
to_symbol: Quote currency for volume calculation. Defaults to 'USD'
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Dict containing the top volume data
|
|
214
|
+
"""
|
|
215
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/top/totalvolfull"
|
|
216
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
217
|
+
|
|
218
|
+
# Ensure to_symbol is a string, not a list
|
|
219
|
+
if isinstance(to_symbol, list):
|
|
220
|
+
to_symbol = to_symbol[0] if to_symbol else "USD"
|
|
221
|
+
|
|
222
|
+
params = {"limit": limit, "tsym": to_symbol.upper()}
|
|
223
|
+
async with httpx.AsyncClient() as client:
|
|
224
|
+
response = await client.get(url, params=params, headers=headers)
|
|
225
|
+
if response.status_code != 200:
|
|
226
|
+
logger.error(f"API returned status code {response.status_code}")
|
|
227
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
228
|
+
return response.json()
|
|
229
|
+
|
|
230
|
+
async def fetch_news(self, api_key: str, token: str, timestamp: int = None) -> dict:
|
|
231
|
+
"""Fetch news for a specific token and timestamp.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
api_key: The CryptoCompare API key
|
|
235
|
+
token: Token symbol to fetch news for (e.g., BTC, ETH, SOL)
|
|
236
|
+
timestamp: Optional timestamp for fetching news
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Dict containing the news data
|
|
240
|
+
"""
|
|
241
|
+
url = f"{CRYPTO_COMPARE_BASE_URL}/data/v2/news/"
|
|
242
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
243
|
+
|
|
244
|
+
# Ensure token is a string, not a list
|
|
245
|
+
if isinstance(token, list):
|
|
246
|
+
token = token[0] if token else ""
|
|
247
|
+
|
|
248
|
+
params = {"categories": token.upper()}
|
|
249
|
+
if timestamp:
|
|
250
|
+
params["lTs"] = timestamp
|
|
251
|
+
|
|
252
|
+
async with httpx.AsyncClient() as client:
|
|
253
|
+
response = await client.get(url, params=params, headers=headers)
|
|
254
|
+
if response.status_code != 200:
|
|
255
|
+
logger.error(f"API returned status code {response.status_code}")
|
|
256
|
+
return {"error": f"API returned status code {response.status_code}"}
|
|
257
|
+
return response.json()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# Response Models
|
|
261
|
+
class CryptoPrice(BaseModel):
|
|
262
|
+
"""Model representing a cryptocurrency price."""
|
|
263
|
+
|
|
264
|
+
from_symbol: str
|
|
265
|
+
to_symbol: str
|
|
266
|
+
price: float
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class CryptoNews(BaseModel):
|
|
270
|
+
"""Model representing a cryptocurrency news article."""
|
|
271
|
+
|
|
272
|
+
id: str
|
|
273
|
+
published_on: int
|
|
274
|
+
title: str
|
|
275
|
+
url: str
|
|
276
|
+
body: str
|
|
277
|
+
tags: str
|
|
278
|
+
categories: str
|
|
279
|
+
source: str
|
|
280
|
+
source_info: Dict[str, Any] = Field(default_factory=dict)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class CryptoExchange(BaseModel):
|
|
284
|
+
"""Model representing a cryptocurrency exchange."""
|
|
285
|
+
|
|
286
|
+
exchange: str
|
|
287
|
+
from_symbol: str
|
|
288
|
+
to_symbol: str
|
|
289
|
+
volume24h: float
|
|
290
|
+
volume24h_to: float
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class CryptoCurrency(BaseModel):
|
|
294
|
+
"""Model representing a cryptocurrency."""
|
|
295
|
+
|
|
296
|
+
id: str
|
|
297
|
+
name: str
|
|
298
|
+
symbol: str
|
|
299
|
+
full_name: str
|
|
300
|
+
market_cap: float = 0
|
|
301
|
+
volume24h: float = 0
|
|
302
|
+
price: float = 0
|
|
303
|
+
change24h: float = 0
|
|
Binary file
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Tool for fetching cryptocurrency news via CryptoCompare API."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import List, Type
|
|
5
|
+
|
|
6
|
+
from langchain_core.runnables import RunnableConfig
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from intentkit.skills.cryptocompare.base import CryptoCompareBaseTool, CryptoNews
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CryptoCompareFetchNewsInput(BaseModel):
|
|
15
|
+
"""Input for CryptoCompareFetchNews tool."""
|
|
16
|
+
|
|
17
|
+
token: str = Field(
|
|
18
|
+
..., description="Token symbol to fetch news for (e.g., BTC, ETH, SOL)"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CryptoCompareFetchNews(CryptoCompareBaseTool):
|
|
23
|
+
"""Tool for fetching cryptocurrency news from CryptoCompare.
|
|
24
|
+
|
|
25
|
+
This tool uses the CryptoCompare API to retrieve the latest news articles
|
|
26
|
+
related to a specific cryptocurrency token.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
name: The name of the tool.
|
|
30
|
+
description: A description of what the tool does.
|
|
31
|
+
args_schema: The schema for the tool's input arguments.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
name: str = "cryptocompare_fetch_news"
|
|
35
|
+
description: str = "Fetch the latest cryptocurrency news for a specific token"
|
|
36
|
+
args_schema: Type[BaseModel] = CryptoCompareFetchNewsInput
|
|
37
|
+
|
|
38
|
+
async def _arun(
|
|
39
|
+
self,
|
|
40
|
+
token: str,
|
|
41
|
+
config: RunnableConfig,
|
|
42
|
+
**kwargs,
|
|
43
|
+
) -> List[CryptoNews]:
|
|
44
|
+
"""Async implementation of the tool to fetch cryptocurrency news.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
token: Token symbol to fetch news for (e.g., BTC, ETH, SOL)
|
|
48
|
+
config: The configuration for the runnable, containing agent context.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List[CryptoNews]: A list of cryptocurrency news articles.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
Exception: If there's an error accessing the CryptoCompare API.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
context = self.context_from_config(config)
|
|
58
|
+
|
|
59
|
+
# Check rate limit
|
|
60
|
+
await self.check_rate_limit(context.agent.id, max_requests=5, interval=60)
|
|
61
|
+
|
|
62
|
+
# Get API key from context
|
|
63
|
+
api_key = context.config.get("api_key")
|
|
64
|
+
if not api_key:
|
|
65
|
+
raise ValueError("CryptoCompare API key not found in configuration")
|
|
66
|
+
|
|
67
|
+
# Fetch news data directly
|
|
68
|
+
news_data = await self.fetch_news(api_key, token)
|
|
69
|
+
|
|
70
|
+
# Check for errors
|
|
71
|
+
if "error" in news_data:
|
|
72
|
+
raise ValueError(news_data["error"])
|
|
73
|
+
|
|
74
|
+
# Convert to list of CryptoNews objects
|
|
75
|
+
result = []
|
|
76
|
+
if "Data" in news_data and news_data["Data"]:
|
|
77
|
+
for article in news_data["Data"]:
|
|
78
|
+
result.append(
|
|
79
|
+
CryptoNews(
|
|
80
|
+
id=str(article["id"]),
|
|
81
|
+
published_on=article["published_on"],
|
|
82
|
+
title=article["title"],
|
|
83
|
+
url=article["url"],
|
|
84
|
+
body=article["body"],
|
|
85
|
+
tags=article.get("tags", ""),
|
|
86
|
+
categories=article.get("categories", ""),
|
|
87
|
+
source=article["source"],
|
|
88
|
+
source_info=article.get("source_info", {}),
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error("Error fetching news: %s", str(e))
|
|
96
|
+
raise type(e)(f"[agent:{context.agent.id}]: {e}") from e
|