intentkit 0.8.6.dev2__py3-none-any.whl → 0.8.17__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/agent.py +4 -5
- intentkit/abstracts/engine.py +5 -5
- intentkit/abstracts/graph.py +10 -5
- intentkit/abstracts/skill.py +6 -144
- intentkit/abstracts/twitter.py +4 -5
- intentkit/clients/__init__.py +3 -2
- intentkit/clients/cdp.py +53 -92
- intentkit/clients/twitter.py +56 -57
- intentkit/clients/web3.py +1 -3
- intentkit/config/config.py +5 -0
- intentkit/core/agent.py +16 -388
- intentkit/core/asset.py +64 -18
- intentkit/core/client.py +1 -1
- intentkit/core/credit.py +19 -20
- intentkit/core/engine.py +26 -11
- intentkit/core/node.py +2 -1
- intentkit/core/prompt.py +53 -15
- intentkit/core/scheduler.py +9 -9
- intentkit/core/statistics.py +6 -7
- intentkit/models/agent.py +256 -176
- intentkit/models/agent_data.py +62 -36
- intentkit/models/agent_schema.json +6 -9
- intentkit/models/app_setting.py +6 -6
- intentkit/models/chat.py +28 -24
- intentkit/models/conversation.py +8 -8
- intentkit/models/credit.py +62 -64
- intentkit/models/db.py +8 -7
- intentkit/models/db_mig.py +2 -2
- intentkit/models/llm.csv +15 -12
- intentkit/models/llm.py +18 -16
- intentkit/models/redis.py +2 -3
- intentkit/models/skill.py +62 -66
- intentkit/models/skills.csv +30 -26
- intentkit/models/user.py +46 -21
- intentkit/skills/acolyt/__init__.py +2 -9
- intentkit/skills/acolyt/ask.py +3 -4
- intentkit/skills/acolyt/base.py +4 -9
- intentkit/skills/aixbt/__init__.py +2 -13
- intentkit/skills/aixbt/base.py +1 -7
- intentkit/skills/aixbt/projects.py +14 -15
- intentkit/skills/allora/__init__.py +2 -9
- intentkit/skills/allora/base.py +4 -9
- intentkit/skills/allora/price.py +3 -4
- intentkit/skills/base.py +175 -52
- intentkit/skills/basename/__init__.py +4 -8
- intentkit/skills/carv/__init__.py +115 -121
- intentkit/skills/carv/base.py +184 -185
- intentkit/skills/carv/fetch_news.py +3 -3
- intentkit/skills/carv/onchain_query.py +4 -4
- intentkit/skills/carv/token_info_and_price.py +5 -5
- intentkit/skills/casino/__init__.py +4 -15
- intentkit/skills/casino/base.py +1 -7
- intentkit/skills/casino/deck_draw.py +5 -8
- intentkit/skills/casino/deck_shuffle.py +6 -6
- intentkit/skills/casino/dice_roll.py +2 -4
- intentkit/skills/cdp/__init__.py +3 -10
- intentkit/skills/cdp/base.py +1 -7
- intentkit/skills/cdp/schema.json +1 -17
- intentkit/skills/chainlist/__init__.py +2 -7
- intentkit/skills/chainlist/base.py +1 -7
- intentkit/skills/chainlist/chain_lookup.py +18 -18
- intentkit/skills/common/__init__.py +2 -9
- intentkit/skills/common/base.py +1 -7
- intentkit/skills/common/current_time.py +1 -2
- intentkit/skills/cookiefun/__init__.py +6 -9
- intentkit/skills/cookiefun/base.py +2 -7
- intentkit/skills/cookiefun/get_account_details.py +7 -7
- intentkit/skills/cookiefun/get_account_feed.py +19 -19
- intentkit/skills/cookiefun/get_account_smart_followers.py +7 -7
- intentkit/skills/cookiefun/get_sectors.py +3 -3
- intentkit/skills/cookiefun/search_accounts.py +9 -9
- intentkit/skills/cryptocompare/__init__.py +7 -24
- intentkit/skills/cryptocompare/api.py +2 -3
- intentkit/skills/cryptocompare/base.py +10 -24
- intentkit/skills/cryptocompare/fetch_news.py +4 -5
- intentkit/skills/cryptocompare/fetch_price.py +6 -7
- intentkit/skills/cryptocompare/fetch_top_exchanges.py +4 -5
- intentkit/skills/cryptocompare/fetch_top_market_cap.py +4 -5
- intentkit/skills/cryptocompare/fetch_top_volume.py +4 -5
- intentkit/skills/cryptocompare/fetch_trading_signals.py +5 -6
- intentkit/skills/cryptopanic/__init__.py +7 -10
- intentkit/skills/cryptopanic/base.py +51 -55
- intentkit/skills/cryptopanic/fetch_crypto_news.py +4 -8
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +5 -7
- intentkit/skills/dapplooker/__init__.py +2 -9
- intentkit/skills/dapplooker/base.py +4 -9
- intentkit/skills/dapplooker/dapplooker_token_data.py +7 -7
- intentkit/skills/defillama/__init__.py +24 -74
- intentkit/skills/defillama/api.py +6 -9
- intentkit/skills/defillama/base.py +8 -19
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +8 -10
- intentkit/skills/defillama/coins/fetch_block.py +6 -8
- intentkit/skills/defillama/coins/fetch_current_prices.py +8 -10
- intentkit/skills/defillama/coins/fetch_first_price.py +7 -9
- intentkit/skills/defillama/coins/fetch_historical_prices.py +9 -11
- intentkit/skills/defillama/coins/fetch_price_chart.py +9 -11
- intentkit/skills/defillama/coins/fetch_price_percentage.py +7 -9
- intentkit/skills/defillama/config/chains.py +1 -3
- intentkit/skills/defillama/fees/fetch_fees_overview.py +24 -26
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +16 -18
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +8 -10
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +5 -7
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +7 -9
- intentkit/skills/defillama/tests/api_integration.test.py +1 -1
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +4 -6
- intentkit/skills/defillama/tvl/fetch_chains.py +9 -11
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +4 -6
- intentkit/skills/defillama/tvl/fetch_protocol.py +32 -38
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +3 -5
- intentkit/skills/defillama/tvl/fetch_protocols.py +37 -45
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +42 -48
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +35 -37
- intentkit/skills/defillama/volumes/fetch_options_overview.py +24 -28
- intentkit/skills/defillama/yields/fetch_pool_chart.py +10 -12
- intentkit/skills/defillama/yields/fetch_pools.py +26 -30
- intentkit/skills/dexscreener/__init__.py +97 -102
- intentkit/skills/dexscreener/base.py +125 -130
- intentkit/skills/dexscreener/get_pair_info.py +4 -5
- intentkit/skills/dexscreener/get_token_pairs.py +4 -5
- intentkit/skills/dexscreener/get_tokens_info.py +7 -8
- intentkit/skills/dexscreener/model/search_token_response.py +80 -82
- intentkit/skills/dexscreener/search_token.py +182 -184
- intentkit/skills/dexscreener/utils.py +15 -14
- intentkit/skills/dune_analytics/__init__.py +7 -9
- intentkit/skills/dune_analytics/base.py +48 -52
- intentkit/skills/dune_analytics/fetch_kol_buys.py +5 -7
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +6 -8
- intentkit/skills/elfa/__init__.py +5 -18
- intentkit/skills/elfa/base.py +10 -14
- intentkit/skills/elfa/mention.py +19 -21
- intentkit/skills/elfa/stats.py +4 -4
- intentkit/skills/elfa/tokens.py +12 -12
- intentkit/skills/elfa/utils.py +26 -28
- intentkit/skills/enso/__init__.py +11 -31
- intentkit/skills/enso/base.py +9 -15
- intentkit/skills/enso/best_yield.py +5 -7
- intentkit/skills/enso/networks.py +3 -9
- intentkit/skills/enso/prices.py +2 -4
- intentkit/skills/enso/route.py +6 -12
- intentkit/skills/enso/tokens.py +4 -16
- intentkit/skills/enso/wallet.py +6 -6
- intentkit/skills/erc20/__init__.py +5 -11
- intentkit/skills/erc721/__init__.py +5 -9
- intentkit/skills/firecrawl/__init__.py +5 -18
- intentkit/skills/firecrawl/base.py +4 -9
- intentkit/skills/firecrawl/clear.py +4 -8
- intentkit/skills/firecrawl/crawl.py +19 -19
- intentkit/skills/firecrawl/query.py +4 -3
- intentkit/skills/firecrawl/scrape.py +17 -22
- intentkit/skills/firecrawl/utils.py +50 -42
- intentkit/skills/github/__init__.py +2 -7
- intentkit/skills/github/base.py +1 -7
- intentkit/skills/github/github_search.py +1 -2
- intentkit/skills/heurist/__init__.py +8 -27
- intentkit/skills/heurist/base.py +4 -9
- intentkit/skills/heurist/image_generation_animagine_xl.py +12 -13
- intentkit/skills/heurist/image_generation_arthemy_comics.py +12 -13
- intentkit/skills/heurist/image_generation_arthemy_real.py +12 -13
- intentkit/skills/heurist/image_generation_braindance.py +12 -13
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +12 -13
- intentkit/skills/heurist/image_generation_flux_1_dev.py +12 -13
- intentkit/skills/heurist/image_generation_sdxl.py +12 -13
- intentkit/skills/http/__init__.py +4 -15
- intentkit/skills/http/base.py +1 -7
- intentkit/skills/http/get.py +21 -16
- intentkit/skills/http/post.py +23 -18
- intentkit/skills/http/put.py +23 -18
- intentkit/skills/lifi/__init__.py +5 -10
- intentkit/skills/lifi/base.py +1 -7
- intentkit/skills/lifi/token_execute.py +14 -17
- intentkit/skills/lifi/token_quote.py +7 -9
- intentkit/skills/lifi/utils.py +16 -16
- intentkit/skills/moralis/__init__.py +6 -10
- intentkit/skills/moralis/api.py +6 -7
- intentkit/skills/moralis/base.py +5 -10
- intentkit/skills/moralis/fetch_chain_portfolio.py +10 -11
- intentkit/skills/moralis/fetch_nft_portfolio.py +22 -22
- intentkit/skills/moralis/fetch_solana_portfolio.py +11 -12
- intentkit/skills/moralis/fetch_wallet_portfolio.py +8 -9
- intentkit/skills/morpho/__init__.py +5 -9
- intentkit/skills/nation/__init__.py +4 -9
- intentkit/skills/nation/base.py +5 -10
- intentkit/skills/nation/nft_check.py +3 -4
- intentkit/skills/onchain.py +23 -0
- intentkit/skills/openai/__init__.py +17 -18
- intentkit/skills/openai/base.py +10 -14
- intentkit/skills/openai/dalle_image_generation.py +3 -8
- intentkit/skills/openai/gpt_avatar_generator.py +102 -0
- intentkit/skills/openai/gpt_image_generation.py +4 -8
- intentkit/skills/openai/gpt_image_mini_generator.py +91 -0
- intentkit/skills/openai/gpt_image_to_image.py +4 -8
- intentkit/skills/openai/image_to_text.py +3 -7
- intentkit/skills/openai/schema.json +32 -0
- intentkit/skills/portfolio/__init__.py +11 -35
- intentkit/skills/portfolio/base.py +33 -19
- intentkit/skills/portfolio/token_balances.py +21 -21
- intentkit/skills/portfolio/wallet_approvals.py +17 -18
- intentkit/skills/portfolio/wallet_defi_positions.py +3 -3
- intentkit/skills/portfolio/wallet_history.py +31 -31
- intentkit/skills/portfolio/wallet_net_worth.py +13 -13
- intentkit/skills/portfolio/wallet_nfts.py +19 -19
- intentkit/skills/portfolio/wallet_profitability.py +18 -18
- intentkit/skills/portfolio/wallet_profitability_summary.py +5 -5
- intentkit/skills/portfolio/wallet_stats.py +3 -3
- intentkit/skills/portfolio/wallet_swaps.py +19 -19
- intentkit/skills/pyth/__init__.py +4 -10
- intentkit/skills/skills.toml +4 -0
- intentkit/skills/slack/__init__.py +5 -17
- intentkit/skills/slack/base.py +3 -9
- intentkit/skills/slack/get_channel.py +8 -8
- intentkit/skills/slack/get_message.py +9 -9
- intentkit/skills/slack/schedule_message.py +5 -5
- intentkit/skills/slack/send_message.py +3 -5
- intentkit/skills/supabase/__init__.py +7 -23
- intentkit/skills/supabase/base.py +1 -7
- intentkit/skills/supabase/delete_data.py +4 -4
- intentkit/skills/supabase/fetch_data.py +12 -12
- intentkit/skills/supabase/insert_data.py +4 -4
- intentkit/skills/supabase/invoke_function.py +6 -6
- intentkit/skills/supabase/update_data.py +6 -6
- intentkit/skills/supabase/upsert_data.py +4 -4
- intentkit/skills/superfluid/__init__.py +5 -9
- intentkit/skills/system/__init__.py +7 -24
- intentkit/skills/system/add_autonomous_task.py +10 -12
- intentkit/skills/system/delete_autonomous_task.py +2 -2
- intentkit/skills/system/edit_autonomous_task.py +14 -18
- intentkit/skills/system/list_autonomous_tasks.py +3 -5
- 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 +4 -9
- intentkit/skills/tavily/tavily_extract.py +2 -4
- intentkit/skills/tavily/tavily_search.py +4 -6
- intentkit/skills/token/__init__.py +5 -10
- intentkit/skills/token/base.py +7 -11
- intentkit/skills/token/erc20_transfers.py +19 -19
- intentkit/skills/token/token_analytics.py +3 -3
- intentkit/skills/token/token_price.py +13 -13
- intentkit/skills/token/token_search.py +9 -9
- intentkit/skills/twitter/__init__.py +11 -35
- intentkit/skills/twitter/base.py +22 -34
- intentkit/skills/twitter/follow_user.py +2 -6
- intentkit/skills/twitter/get_mentions.py +5 -12
- intentkit/skills/twitter/get_timeline.py +4 -12
- intentkit/skills/twitter/get_user_by_username.py +2 -6
- intentkit/skills/twitter/get_user_tweets.py +5 -13
- intentkit/skills/twitter/like_tweet.py +2 -6
- intentkit/skills/twitter/post_tweet.py +6 -9
- intentkit/skills/twitter/reply_tweet.py +6 -9
- intentkit/skills/twitter/retweet.py +2 -6
- intentkit/skills/twitter/search_tweets.py +4 -12
- intentkit/skills/unrealspeech/__init__.py +2 -7
- intentkit/skills/unrealspeech/base.py +2 -8
- intentkit/skills/unrealspeech/text_to_speech.py +8 -8
- intentkit/skills/venice_audio/__init__.py +98 -106
- intentkit/skills/venice_audio/base.py +117 -121
- intentkit/skills/venice_audio/input.py +41 -41
- intentkit/skills/venice_audio/venice_audio.py +7 -11
- intentkit/skills/venice_image/__init__.py +147 -154
- intentkit/skills/venice_image/api.py +138 -138
- intentkit/skills/venice_image/base.py +185 -192
- intentkit/skills/venice_image/config.py +33 -35
- intentkit/skills/venice_image/image_enhance/image_enhance.py +2 -3
- intentkit/skills/venice_image/image_enhance/image_enhance_base.py +21 -23
- intentkit/skills/venice_image/image_enhance/image_enhance_input.py +38 -40
- intentkit/skills/venice_image/image_generation/image_generation_base.py +9 -9
- intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -27
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -158
- intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -28
- intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -28
- intentkit/skills/venice_image/image_upscale/image_upscale.py +3 -3
- intentkit/skills/venice_image/image_upscale/image_upscale_base.py +21 -23
- intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -22
- intentkit/skills/venice_image/image_vision/image_vision.py +2 -2
- intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -17
- intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -9
- intentkit/skills/venice_image/utils.py +77 -78
- intentkit/skills/web_scraper/__init__.py +5 -18
- intentkit/skills/web_scraper/base.py +21 -7
- intentkit/skills/web_scraper/document_indexer.py +7 -6
- intentkit/skills/web_scraper/scrape_and_index.py +15 -15
- intentkit/skills/web_scraper/utils.py +62 -63
- intentkit/skills/web_scraper/website_indexer.py +17 -19
- intentkit/skills/weth/__init__.py +5 -11
- intentkit/skills/wow/__init__.py +5 -11
- intentkit/skills/x402/__init__.py +61 -0
- intentkit/skills/x402/ask_agent.py +98 -0
- intentkit/skills/x402/base.py +99 -0
- intentkit/skills/x402/http_request.py +117 -0
- intentkit/skills/x402/schema.json +45 -0
- intentkit/skills/x402/x402.webp +0 -0
- intentkit/skills/xmtp/__init__.py +4 -15
- intentkit/skills/xmtp/base.py +5 -5
- intentkit/skills/xmtp/price.py +6 -6
- intentkit/skills/xmtp/swap.py +6 -8
- intentkit/skills/xmtp/transfer.py +4 -6
- intentkit/utils/error.py +2 -2
- intentkit/utils/logging.py +2 -4
- intentkit/utils/s3.py +8 -9
- intentkit/utils/schema.py +100 -0
- intentkit/utils/slack_alert.py +7 -8
- {intentkit-0.8.6.dev2.dist-info → intentkit-0.8.17.dist-info}/METADATA +3 -4
- intentkit-0.8.17.dist-info/RECORD +466 -0
- intentkit/models/generator.py +0 -347
- intentkit-0.8.6.dev2.dist-info/RECORD +0 -457
- {intentkit-0.8.6.dev2.dist-info → intentkit-0.8.17.dist-info}/WHEEL +0 -0
- {intentkit-0.8.6.dev2.dist-info → intentkit-0.8.17.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,184 +1,182 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from pydantic import BaseModel, Field, ValidationError
|
|
5
|
-
|
|
6
|
-
from intentkit.skills.dexscreener.base import DexScreenerBaseTool
|
|
7
|
-
from intentkit.skills.dexscreener.model.search_token_response import (
|
|
8
|
-
SearchTokenResponseModel,
|
|
9
|
-
)
|
|
10
|
-
from intentkit.skills.dexscreener.utils import (
|
|
11
|
-
API_ENDPOINTS,
|
|
12
|
-
MAX_SEARCH_RESULTS,
|
|
13
|
-
SEARCH_DISCLAIMER,
|
|
14
|
-
QueryType,
|
|
15
|
-
SortBy,
|
|
16
|
-
VolumeTimeframe,
|
|
17
|
-
create_error_response,
|
|
18
|
-
create_no_results_response,
|
|
19
|
-
determine_query_type,
|
|
20
|
-
filter_address_pairs,
|
|
21
|
-
filter_ticker_pairs,
|
|
22
|
-
format_success_response,
|
|
23
|
-
handle_validation_error,
|
|
24
|
-
sort_pairs_by_criteria,
|
|
25
|
-
truncate_large_fields,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
logger = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class SearchTokenInput(BaseModel):
|
|
32
|
-
"""Input schema for the DexScreener search_token tool."""
|
|
33
|
-
|
|
34
|
-
query: str = Field(
|
|
35
|
-
description="The search query string (e.g., token symbol 'WIF', pair address, token address '0x...', token name 'Dogwifhat', or ticker '$WIF'). Prefixing with '$' filters results to match the base token symbol exactly (case-insensitive)."
|
|
36
|
-
)
|
|
37
|
-
sort_by:
|
|
38
|
-
default=SortBy.LIQUIDITY,
|
|
39
|
-
description="Sort preference for the results. Options: 'liquidity' (default) or 'volume'",
|
|
40
|
-
)
|
|
41
|
-
volume_timeframe:
|
|
42
|
-
default=VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
43
|
-
description="Define which timeframe should we use if the 'sort_by' is 'volume'. Available options: '5_minutes', '1_hour', '6_hour', '24_hour'",
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class SearchToken(DexScreenerBaseTool):
|
|
48
|
-
"""
|
|
49
|
-
Tool to search for token pairs on DexScreener based on a query string.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
name: str = "dexscreener_search_token"
|
|
53
|
-
description: str = (
|
|
54
|
-
f"Searches DexScreener for token pairs matching the provided query string "
|
|
55
|
-
f"(e.g., token symbol like 'WIF', pair address, token name like 'Dogwifhat', or ticker like '$WIF'). "
|
|
56
|
-
f"If the query starts with '$', it filters results to only include pairs where the base token symbol exactly matches the ticker (case-insensitive). "
|
|
57
|
-
f"Returns a list of matching pairs with details like price, volume, liquidity, etc., "
|
|
58
|
-
f"sorted by the specified criteria (via 'sort_by': 'liquidity', 'volume'; defaults to 'liquidity'), "
|
|
59
|
-
f"limited to the top {MAX_SEARCH_RESULTS}. "
|
|
60
|
-
f"Use this tool to find token information based on user queries."
|
|
61
|
-
)
|
|
62
|
-
args_schema:
|
|
63
|
-
|
|
64
|
-
async def _arun(
|
|
65
|
-
self,
|
|
66
|
-
query: str,
|
|
67
|
-
sort_by:
|
|
68
|
-
volume_timeframe:
|
|
69
|
-
**kwargs: Any,
|
|
70
|
-
) -> str:
|
|
71
|
-
"""Implementation to search token, with filtering based on query type."""
|
|
72
|
-
|
|
73
|
-
# dexscreener 300 request per minute (across all user) based on dexscreener docs
|
|
74
|
-
# https://docs.dexscreener.com/api/reference#get-latest-dex-search
|
|
75
|
-
await self.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
f"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
"
|
|
163
|
-
|
|
164
|
-
"
|
|
165
|
-
|
|
166
|
-
logger.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
additional_data={"query": query},
|
|
184
|
-
)
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field, ValidationError
|
|
5
|
+
|
|
6
|
+
from intentkit.skills.dexscreener.base import DexScreenerBaseTool
|
|
7
|
+
from intentkit.skills.dexscreener.model.search_token_response import (
|
|
8
|
+
SearchTokenResponseModel,
|
|
9
|
+
)
|
|
10
|
+
from intentkit.skills.dexscreener.utils import (
|
|
11
|
+
API_ENDPOINTS,
|
|
12
|
+
MAX_SEARCH_RESULTS,
|
|
13
|
+
SEARCH_DISCLAIMER,
|
|
14
|
+
QueryType,
|
|
15
|
+
SortBy,
|
|
16
|
+
VolumeTimeframe,
|
|
17
|
+
create_error_response,
|
|
18
|
+
create_no_results_response,
|
|
19
|
+
determine_query_type,
|
|
20
|
+
filter_address_pairs,
|
|
21
|
+
filter_ticker_pairs,
|
|
22
|
+
format_success_response,
|
|
23
|
+
handle_validation_error,
|
|
24
|
+
sort_pairs_by_criteria,
|
|
25
|
+
truncate_large_fields,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SearchTokenInput(BaseModel):
|
|
32
|
+
"""Input schema for the DexScreener search_token tool."""
|
|
33
|
+
|
|
34
|
+
query: str = Field(
|
|
35
|
+
description="The search query string (e.g., token symbol 'WIF', pair address, token address '0x...', token name 'Dogwifhat', or ticker '$WIF'). Prefixing with '$' filters results to match the base token symbol exactly (case-insensitive)."
|
|
36
|
+
)
|
|
37
|
+
sort_by: SortBy | None = Field(
|
|
38
|
+
default=SortBy.LIQUIDITY,
|
|
39
|
+
description="Sort preference for the results. Options: 'liquidity' (default) or 'volume'",
|
|
40
|
+
)
|
|
41
|
+
volume_timeframe: VolumeTimeframe | None = Field(
|
|
42
|
+
default=VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
43
|
+
description="Define which timeframe should we use if the 'sort_by' is 'volume'. Available options: '5_minutes', '1_hour', '6_hour', '24_hour'",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SearchToken(DexScreenerBaseTool):
|
|
48
|
+
"""
|
|
49
|
+
Tool to search for token pairs on DexScreener based on a query string.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str = "dexscreener_search_token"
|
|
53
|
+
description: str = (
|
|
54
|
+
f"Searches DexScreener for token pairs matching the provided query string "
|
|
55
|
+
f"(e.g., token symbol like 'WIF', pair address, token name like 'Dogwifhat', or ticker like '$WIF'). "
|
|
56
|
+
f"If the query starts with '$', it filters results to only include pairs where the base token symbol exactly matches the ticker (case-insensitive). "
|
|
57
|
+
f"Returns a list of matching pairs with details like price, volume, liquidity, etc., "
|
|
58
|
+
f"sorted by the specified criteria (via 'sort_by': 'liquidity', 'volume'; defaults to 'liquidity'), "
|
|
59
|
+
f"limited to the top {MAX_SEARCH_RESULTS}. "
|
|
60
|
+
f"Use this tool to find token information based on user queries."
|
|
61
|
+
)
|
|
62
|
+
args_schema: type[BaseModel] = SearchTokenInput
|
|
63
|
+
|
|
64
|
+
async def _arun(
|
|
65
|
+
self,
|
|
66
|
+
query: str,
|
|
67
|
+
sort_by: SortBy | None = SortBy.LIQUIDITY,
|
|
68
|
+
volume_timeframe: VolumeTimeframe | None = VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
69
|
+
**kwargs: Any,
|
|
70
|
+
) -> str:
|
|
71
|
+
"""Implementation to search token, with filtering based on query type."""
|
|
72
|
+
|
|
73
|
+
# dexscreener 300 request per minute (across all user) based on dexscreener docs
|
|
74
|
+
# https://docs.dexscreener.com/api/reference#get-latest-dex-search
|
|
75
|
+
await self.global_rate_limit_by_skill(
|
|
76
|
+
limit=300,
|
|
77
|
+
seconds=60,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
sort_by = sort_by or SortBy.LIQUIDITY
|
|
81
|
+
volume_timeframe = volume_timeframe or VolumeTimeframe.TWENTY_FOUR_HOUR
|
|
82
|
+
|
|
83
|
+
# Determine query type
|
|
84
|
+
query_type = determine_query_type(query)
|
|
85
|
+
|
|
86
|
+
# Process query based on type
|
|
87
|
+
if query_type == QueryType.TICKER:
|
|
88
|
+
search_query = query[1:] # Remove the '$' prefix
|
|
89
|
+
target_ticker = search_query.upper()
|
|
90
|
+
else:
|
|
91
|
+
search_query = query
|
|
92
|
+
target_ticker = None
|
|
93
|
+
|
|
94
|
+
logger.info(
|
|
95
|
+
f"Executing DexScreener search_token tool with query: '{query}' "
|
|
96
|
+
f"(interpreted as {query_type.value} search for '{search_query}'), "
|
|
97
|
+
f"sort_by: {sort_by}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
data, error_details = await self._get(
|
|
102
|
+
path=API_ENDPOINTS["search"], params={"q": search_query}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if error_details:
|
|
106
|
+
return await self._handle_error_response(error_details)
|
|
107
|
+
if not data:
|
|
108
|
+
logger.error(f"No data or error details returned for query '{query}'")
|
|
109
|
+
return create_error_response(
|
|
110
|
+
error_type="empty_success",
|
|
111
|
+
message="API call returned empty success response.",
|
|
112
|
+
additional_data={"query": query},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
result = SearchTokenResponseModel.model_validate(data)
|
|
117
|
+
except ValidationError as e:
|
|
118
|
+
return handle_validation_error(e, query, len(str(data)))
|
|
119
|
+
|
|
120
|
+
if not result.pairs:
|
|
121
|
+
return create_no_results_response(
|
|
122
|
+
query, reason="returned null or empty for pairs"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
pairs_list = [p for p in result.pairs if p is not None]
|
|
126
|
+
|
|
127
|
+
# Apply filtering based on query type
|
|
128
|
+
if query_type == QueryType.TICKER and target_ticker:
|
|
129
|
+
pairs_list = filter_ticker_pairs(pairs_list, target_ticker)
|
|
130
|
+
if not pairs_list:
|
|
131
|
+
return create_no_results_response(
|
|
132
|
+
query, reason=f"no match for ticker '${target_ticker}'"
|
|
133
|
+
)
|
|
134
|
+
elif query_type == QueryType.ADDRESS:
|
|
135
|
+
pairs_list = filter_address_pairs(pairs_list, search_query)
|
|
136
|
+
if not pairs_list:
|
|
137
|
+
return create_no_results_response(
|
|
138
|
+
query, reason=f"no match for address '{search_query}'"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Sort pairs by specified criteria
|
|
142
|
+
pairs_list = sort_pairs_by_criteria(pairs_list, sort_by, volume_timeframe)
|
|
143
|
+
|
|
144
|
+
# If sorting failed, pairs_list will be returned unchanged by the utility function
|
|
145
|
+
|
|
146
|
+
final_count = min(len(pairs_list), MAX_SEARCH_RESULTS)
|
|
147
|
+
logger.info(f"Returning {final_count} pairs for query '{query}'")
|
|
148
|
+
return format_success_response(
|
|
149
|
+
{
|
|
150
|
+
**SEARCH_DISCLAIMER,
|
|
151
|
+
"pairs": [p.model_dump() for p in pairs_list[:MAX_SEARCH_RESULTS]],
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
return await self._handle_unexpected_runtime_error(e, query)
|
|
156
|
+
|
|
157
|
+
async def _handle_error_response(self, error_details: dict) -> str:
|
|
158
|
+
"""Formats error details (from _get) into a JSON string."""
|
|
159
|
+
if error_details.get("error_type") in [
|
|
160
|
+
"connection_error",
|
|
161
|
+
"parsing_error",
|
|
162
|
+
"unexpected_error",
|
|
163
|
+
]:
|
|
164
|
+
logger.error(f"DexScreener tool encountered an error: {error_details}")
|
|
165
|
+
else: # api_error
|
|
166
|
+
logger.warning(f"DexScreener API returned an error: {error_details}")
|
|
167
|
+
|
|
168
|
+
# Truncate potentially large fields before returning to user/LLM
|
|
169
|
+
truncated_details = truncate_large_fields(error_details)
|
|
170
|
+
return format_success_response(truncated_details)
|
|
171
|
+
|
|
172
|
+
async def _handle_unexpected_runtime_error(self, e: Exception, query: str) -> str:
|
|
173
|
+
"""Formats unexpected runtime exception details into a JSON string."""
|
|
174
|
+
logger.exception(
|
|
175
|
+
f"An unexpected runtime error occurred in search_token tool _arun method for query '{query}': {e}"
|
|
176
|
+
)
|
|
177
|
+
return create_error_response(
|
|
178
|
+
error_type="runtime_error",
|
|
179
|
+
message="An unexpected internal error occurred processing the search request",
|
|
180
|
+
details=str(e),
|
|
181
|
+
additional_data={"query": query},
|
|
182
|
+
)
|
|
@@ -4,8 +4,9 @@ Utility functions and constants for DexScreener skills.
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
|
+
from collections.abc import Callable
|
|
7
8
|
from enum import Enum
|
|
8
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
9
10
|
|
|
10
11
|
from pydantic import ValidationError
|
|
11
12
|
|
|
@@ -175,11 +176,11 @@ def get_sort_function(
|
|
|
175
176
|
|
|
176
177
|
|
|
177
178
|
def sort_pairs_by_criteria(
|
|
178
|
-
pairs:
|
|
179
|
+
pairs: list[PairModel],
|
|
179
180
|
sort_by: SortBy = SortBy.LIQUIDITY,
|
|
180
181
|
volume_timeframe: VolumeTimeframe = VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
181
182
|
reverse: bool = True,
|
|
182
|
-
) ->
|
|
183
|
+
) -> list[PairModel]:
|
|
183
184
|
"""
|
|
184
185
|
Sort pairs by the specified criteria.
|
|
185
186
|
|
|
@@ -200,7 +201,7 @@ def sort_pairs_by_criteria(
|
|
|
200
201
|
return pairs # Return original list if sorting fails
|
|
201
202
|
|
|
202
203
|
|
|
203
|
-
def filter_ticker_pairs(pairs:
|
|
204
|
+
def filter_ticker_pairs(pairs: list[PairModel], target_ticker: str) -> list[PairModel]:
|
|
204
205
|
"""
|
|
205
206
|
Filter pairs to only include those where base token symbol matches target ticker.
|
|
206
207
|
|
|
@@ -222,8 +223,8 @@ def filter_ticker_pairs(pairs: List[PairModel], target_ticker: str) -> List[Pair
|
|
|
222
223
|
|
|
223
224
|
|
|
224
225
|
def filter_address_pairs(
|
|
225
|
-
pairs:
|
|
226
|
-
) ->
|
|
226
|
+
pairs: list[PairModel], target_address: str
|
|
227
|
+
) -> list[PairModel]:
|
|
227
228
|
"""
|
|
228
229
|
Filter pairs to only include those matching the target address.
|
|
229
230
|
Checks pairAddress, baseToken.address, and quoteToken.address.
|
|
@@ -256,8 +257,8 @@ def filter_address_pairs(
|
|
|
256
257
|
def create_error_response(
|
|
257
258
|
error_type: str,
|
|
258
259
|
message: str,
|
|
259
|
-
details:
|
|
260
|
-
additional_data:
|
|
260
|
+
details: str | None = None,
|
|
261
|
+
additional_data: dict[str, Any] | None = None,
|
|
261
262
|
) -> str:
|
|
262
263
|
"""
|
|
263
264
|
Create a standardized error response in JSON format.
|
|
@@ -288,7 +289,7 @@ def create_error_response(
|
|
|
288
289
|
def create_no_results_response(
|
|
289
290
|
query_info: str,
|
|
290
291
|
reason: str = "no results found",
|
|
291
|
-
additional_data:
|
|
292
|
+
additional_data: dict[str, Any] | None = None,
|
|
292
293
|
) -> str:
|
|
293
294
|
"""
|
|
294
295
|
Create a standardized "no results found" response.
|
|
@@ -314,7 +315,7 @@ def create_no_results_response(
|
|
|
314
315
|
|
|
315
316
|
|
|
316
317
|
def handle_validation_error(
|
|
317
|
-
error: ValidationError, query_info: str, data_length:
|
|
318
|
+
error: ValidationError, query_info: str, data_length: int | None = None
|
|
318
319
|
) -> str:
|
|
319
320
|
"""
|
|
320
321
|
Handle validation errors in a standardized way.
|
|
@@ -342,8 +343,8 @@ def handle_validation_error(
|
|
|
342
343
|
|
|
343
344
|
|
|
344
345
|
def truncate_large_fields(
|
|
345
|
-
data:
|
|
346
|
-
) ->
|
|
346
|
+
data: dict[str, Any], max_length: int = 500
|
|
347
|
+
) -> dict[str, Any]:
|
|
347
348
|
"""
|
|
348
349
|
Truncate large string fields in error response data to avoid overwhelming the LLM.
|
|
349
350
|
|
|
@@ -363,7 +364,7 @@ def truncate_large_fields(
|
|
|
363
364
|
return truncated
|
|
364
365
|
|
|
365
366
|
|
|
366
|
-
def group_pairs_by_token(pairs:
|
|
367
|
+
def group_pairs_by_token(pairs: list[PairModel]) -> dict[str, list[PairModel]]:
|
|
367
368
|
"""
|
|
368
369
|
Group pairs by token address for better organization in multi-token responses.
|
|
369
370
|
|
|
@@ -406,7 +407,7 @@ def validate_chain_id(chain_id: str) -> bool:
|
|
|
406
407
|
return chain_id.lower() in SUPPORTED_CHAINS
|
|
407
408
|
|
|
408
409
|
|
|
409
|
-
def format_success_response(data:
|
|
410
|
+
def format_success_response(data: dict[str, Any]) -> str:
|
|
410
411
|
"""
|
|
411
412
|
Format a successful response as JSON string.
|
|
412
413
|
|
|
@@ -4,16 +4,15 @@ Loads and initializes skills for fetching data from Dune Analytics API.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import TypedDict
|
|
8
8
|
|
|
9
|
-
from intentkit.abstracts.skill import SkillStoreABC
|
|
10
9
|
from intentkit.skills.base import SkillConfig, SkillState
|
|
11
10
|
from intentkit.skills.dune_analytics.base import DuneBaseTool
|
|
12
11
|
|
|
13
12
|
logger = logging.getLogger(__name__)
|
|
14
13
|
|
|
15
14
|
# Cache for skill instances
|
|
16
|
-
_skill_cache:
|
|
15
|
+
_skill_cache: dict[str, DuneBaseTool] = {}
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class SkillStates(TypedDict):
|
|
@@ -33,9 +32,8 @@ class Config(SkillConfig):
|
|
|
33
32
|
async def get_skills(
|
|
34
33
|
config: Config,
|
|
35
34
|
is_private: bool,
|
|
36
|
-
store: SkillStoreABC,
|
|
37
35
|
**kwargs,
|
|
38
|
-
) ->
|
|
36
|
+
) -> list[DuneBaseTool]:
|
|
39
37
|
"""Load Dune Analytics skills based on configuration.
|
|
40
38
|
|
|
41
39
|
Args:
|
|
@@ -59,7 +57,7 @@ async def get_skills(
|
|
|
59
57
|
|
|
60
58
|
loaded_skills = []
|
|
61
59
|
for name in available_skills:
|
|
62
|
-
skill = get_dune_skill(name
|
|
60
|
+
skill = get_dune_skill(name)
|
|
63
61
|
if skill:
|
|
64
62
|
logger.info("Successfully loaded skill: %s", name)
|
|
65
63
|
loaded_skills.append(skill)
|
|
@@ -69,7 +67,7 @@ async def get_skills(
|
|
|
69
67
|
return loaded_skills
|
|
70
68
|
|
|
71
69
|
|
|
72
|
-
def get_dune_skill(name: str
|
|
70
|
+
def get_dune_skill(name: str) -> DuneBaseTool | None:
|
|
73
71
|
"""Retrieve a Dune Analytics skill instance by name.
|
|
74
72
|
|
|
75
73
|
Args:
|
|
@@ -87,11 +85,11 @@ def get_dune_skill(name: str, store: SkillStoreABC) -> Optional[DuneBaseTool]:
|
|
|
87
85
|
if name == "fetch_nation_metrics":
|
|
88
86
|
from .fetch_nation_metrics import FetchNationMetrics
|
|
89
87
|
|
|
90
|
-
_skill_cache[name] = FetchNationMetrics(
|
|
88
|
+
_skill_cache[name] = FetchNationMetrics()
|
|
91
89
|
elif name == "fetch_kol_buys":
|
|
92
90
|
from .fetch_kol_buys import FetchKOLBuys
|
|
93
91
|
|
|
94
|
-
_skill_cache[name] = FetchKOLBuys(
|
|
92
|
+
_skill_cache[name] = FetchKOLBuys()
|
|
95
93
|
else:
|
|
96
94
|
logger.warning("Unknown Dune Analytics skill: %s", name)
|
|
97
95
|
return None
|
|
@@ -1,52 +1,48 @@
|
|
|
1
|
-
"""Base module for Dune Analytics skills.
|
|
2
|
-
|
|
3
|
-
Provides shared functionality for interacting with the Dune Analytics API.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from
|
|
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
|
-
@property
|
|
50
|
-
def category(self) -> str:
|
|
51
|
-
"""Category of the skill."""
|
|
52
|
-
return "dune_analytics"
|
|
1
|
+
"""Base module for Dune Analytics skills.
|
|
2
|
+
|
|
3
|
+
Provides shared functionality for interacting with the Dune Analytics API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from langchain_core.tools.base import ToolException
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from intentkit.skills.base import IntentKitSkill
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DuneBaseTool(IntentKitSkill):
|
|
13
|
+
"""Base class for Dune Analytics skills.
|
|
14
|
+
|
|
15
|
+
Offers common functionality like API key retrieval and Dune API interaction.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
name: str = Field(description="Tool name")
|
|
19
|
+
description: str = Field(description="Tool description")
|
|
20
|
+
args_schema: type[BaseModel]
|
|
21
|
+
|
|
22
|
+
def get_api_key(self) -> str:
|
|
23
|
+
"""Retrieve the Dune Analytics API key from context.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
API key string.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ToolException: If the API key is not found.
|
|
30
|
+
"""
|
|
31
|
+
context = self.get_context()
|
|
32
|
+
skill_config = context.agent.skill_config(self.category)
|
|
33
|
+
api_key_provider = skill_config.get("api_key_provider")
|
|
34
|
+
if api_key_provider == "agent_owner":
|
|
35
|
+
api_key = skill_config.get("api_key")
|
|
36
|
+
if api_key:
|
|
37
|
+
return api_key
|
|
38
|
+
else:
|
|
39
|
+
raise ToolException("No api_key found in agent_owner configuration")
|
|
40
|
+
else:
|
|
41
|
+
raise ToolException(
|
|
42
|
+
f"Invalid API key provider: {api_key_provider}. Only 'agent_owner' is supported for Dune Analytics."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def category(self) -> str:
|
|
47
|
+
"""Category of the skill."""
|
|
48
|
+
return "dune_analytics"
|