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.

Files changed (312) hide show
  1. intentkit/__init__.py +1 -1
  2. intentkit/abstracts/agent.py +4 -5
  3. intentkit/abstracts/engine.py +5 -5
  4. intentkit/abstracts/graph.py +10 -5
  5. intentkit/abstracts/skill.py +6 -144
  6. intentkit/abstracts/twitter.py +4 -5
  7. intentkit/clients/__init__.py +3 -2
  8. intentkit/clients/cdp.py +53 -92
  9. intentkit/clients/twitter.py +56 -57
  10. intentkit/clients/web3.py +1 -3
  11. intentkit/config/config.py +5 -0
  12. intentkit/core/agent.py +16 -388
  13. intentkit/core/asset.py +64 -18
  14. intentkit/core/client.py +1 -1
  15. intentkit/core/credit.py +19 -20
  16. intentkit/core/engine.py +26 -11
  17. intentkit/core/node.py +2 -1
  18. intentkit/core/prompt.py +53 -15
  19. intentkit/core/scheduler.py +9 -9
  20. intentkit/core/statistics.py +6 -7
  21. intentkit/models/agent.py +256 -176
  22. intentkit/models/agent_data.py +62 -36
  23. intentkit/models/agent_schema.json +6 -9
  24. intentkit/models/app_setting.py +6 -6
  25. intentkit/models/chat.py +28 -24
  26. intentkit/models/conversation.py +8 -8
  27. intentkit/models/credit.py +62 -64
  28. intentkit/models/db.py +8 -7
  29. intentkit/models/db_mig.py +2 -2
  30. intentkit/models/llm.csv +15 -12
  31. intentkit/models/llm.py +18 -16
  32. intentkit/models/redis.py +2 -3
  33. intentkit/models/skill.py +62 -66
  34. intentkit/models/skills.csv +30 -26
  35. intentkit/models/user.py +46 -21
  36. intentkit/skills/acolyt/__init__.py +2 -9
  37. intentkit/skills/acolyt/ask.py +3 -4
  38. intentkit/skills/acolyt/base.py +4 -9
  39. intentkit/skills/aixbt/__init__.py +2 -13
  40. intentkit/skills/aixbt/base.py +1 -7
  41. intentkit/skills/aixbt/projects.py +14 -15
  42. intentkit/skills/allora/__init__.py +2 -9
  43. intentkit/skills/allora/base.py +4 -9
  44. intentkit/skills/allora/price.py +3 -4
  45. intentkit/skills/base.py +175 -52
  46. intentkit/skills/basename/__init__.py +4 -8
  47. intentkit/skills/carv/__init__.py +115 -121
  48. intentkit/skills/carv/base.py +184 -185
  49. intentkit/skills/carv/fetch_news.py +3 -3
  50. intentkit/skills/carv/onchain_query.py +4 -4
  51. intentkit/skills/carv/token_info_and_price.py +5 -5
  52. intentkit/skills/casino/__init__.py +4 -15
  53. intentkit/skills/casino/base.py +1 -7
  54. intentkit/skills/casino/deck_draw.py +5 -8
  55. intentkit/skills/casino/deck_shuffle.py +6 -6
  56. intentkit/skills/casino/dice_roll.py +2 -4
  57. intentkit/skills/cdp/__init__.py +3 -10
  58. intentkit/skills/cdp/base.py +1 -7
  59. intentkit/skills/cdp/schema.json +1 -17
  60. intentkit/skills/chainlist/__init__.py +2 -7
  61. intentkit/skills/chainlist/base.py +1 -7
  62. intentkit/skills/chainlist/chain_lookup.py +18 -18
  63. intentkit/skills/common/__init__.py +2 -9
  64. intentkit/skills/common/base.py +1 -7
  65. intentkit/skills/common/current_time.py +1 -2
  66. intentkit/skills/cookiefun/__init__.py +6 -9
  67. intentkit/skills/cookiefun/base.py +2 -7
  68. intentkit/skills/cookiefun/get_account_details.py +7 -7
  69. intentkit/skills/cookiefun/get_account_feed.py +19 -19
  70. intentkit/skills/cookiefun/get_account_smart_followers.py +7 -7
  71. intentkit/skills/cookiefun/get_sectors.py +3 -3
  72. intentkit/skills/cookiefun/search_accounts.py +9 -9
  73. intentkit/skills/cryptocompare/__init__.py +7 -24
  74. intentkit/skills/cryptocompare/api.py +2 -3
  75. intentkit/skills/cryptocompare/base.py +10 -24
  76. intentkit/skills/cryptocompare/fetch_news.py +4 -5
  77. intentkit/skills/cryptocompare/fetch_price.py +6 -7
  78. intentkit/skills/cryptocompare/fetch_top_exchanges.py +4 -5
  79. intentkit/skills/cryptocompare/fetch_top_market_cap.py +4 -5
  80. intentkit/skills/cryptocompare/fetch_top_volume.py +4 -5
  81. intentkit/skills/cryptocompare/fetch_trading_signals.py +5 -6
  82. intentkit/skills/cryptopanic/__init__.py +7 -10
  83. intentkit/skills/cryptopanic/base.py +51 -55
  84. intentkit/skills/cryptopanic/fetch_crypto_news.py +4 -8
  85. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +5 -7
  86. intentkit/skills/dapplooker/__init__.py +2 -9
  87. intentkit/skills/dapplooker/base.py +4 -9
  88. intentkit/skills/dapplooker/dapplooker_token_data.py +7 -7
  89. intentkit/skills/defillama/__init__.py +24 -74
  90. intentkit/skills/defillama/api.py +6 -9
  91. intentkit/skills/defillama/base.py +8 -19
  92. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +8 -10
  93. intentkit/skills/defillama/coins/fetch_block.py +6 -8
  94. intentkit/skills/defillama/coins/fetch_current_prices.py +8 -10
  95. intentkit/skills/defillama/coins/fetch_first_price.py +7 -9
  96. intentkit/skills/defillama/coins/fetch_historical_prices.py +9 -11
  97. intentkit/skills/defillama/coins/fetch_price_chart.py +9 -11
  98. intentkit/skills/defillama/coins/fetch_price_percentage.py +7 -9
  99. intentkit/skills/defillama/config/chains.py +1 -3
  100. intentkit/skills/defillama/fees/fetch_fees_overview.py +24 -26
  101. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +16 -18
  102. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +8 -10
  103. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +5 -7
  104. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +7 -9
  105. intentkit/skills/defillama/tests/api_integration.test.py +1 -1
  106. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +4 -6
  107. intentkit/skills/defillama/tvl/fetch_chains.py +9 -11
  108. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +4 -6
  109. intentkit/skills/defillama/tvl/fetch_protocol.py +32 -38
  110. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +3 -5
  111. intentkit/skills/defillama/tvl/fetch_protocols.py +37 -45
  112. intentkit/skills/defillama/volumes/fetch_dex_overview.py +42 -48
  113. intentkit/skills/defillama/volumes/fetch_dex_summary.py +35 -37
  114. intentkit/skills/defillama/volumes/fetch_options_overview.py +24 -28
  115. intentkit/skills/defillama/yields/fetch_pool_chart.py +10 -12
  116. intentkit/skills/defillama/yields/fetch_pools.py +26 -30
  117. intentkit/skills/dexscreener/__init__.py +97 -102
  118. intentkit/skills/dexscreener/base.py +125 -130
  119. intentkit/skills/dexscreener/get_pair_info.py +4 -5
  120. intentkit/skills/dexscreener/get_token_pairs.py +4 -5
  121. intentkit/skills/dexscreener/get_tokens_info.py +7 -8
  122. intentkit/skills/dexscreener/model/search_token_response.py +80 -82
  123. intentkit/skills/dexscreener/search_token.py +182 -184
  124. intentkit/skills/dexscreener/utils.py +15 -14
  125. intentkit/skills/dune_analytics/__init__.py +7 -9
  126. intentkit/skills/dune_analytics/base.py +48 -52
  127. intentkit/skills/dune_analytics/fetch_kol_buys.py +5 -7
  128. intentkit/skills/dune_analytics/fetch_nation_metrics.py +6 -8
  129. intentkit/skills/elfa/__init__.py +5 -18
  130. intentkit/skills/elfa/base.py +10 -14
  131. intentkit/skills/elfa/mention.py +19 -21
  132. intentkit/skills/elfa/stats.py +4 -4
  133. intentkit/skills/elfa/tokens.py +12 -12
  134. intentkit/skills/elfa/utils.py +26 -28
  135. intentkit/skills/enso/__init__.py +11 -31
  136. intentkit/skills/enso/base.py +9 -15
  137. intentkit/skills/enso/best_yield.py +5 -7
  138. intentkit/skills/enso/networks.py +3 -9
  139. intentkit/skills/enso/prices.py +2 -4
  140. intentkit/skills/enso/route.py +6 -12
  141. intentkit/skills/enso/tokens.py +4 -16
  142. intentkit/skills/enso/wallet.py +6 -6
  143. intentkit/skills/erc20/__init__.py +5 -11
  144. intentkit/skills/erc721/__init__.py +5 -9
  145. intentkit/skills/firecrawl/__init__.py +5 -18
  146. intentkit/skills/firecrawl/base.py +4 -9
  147. intentkit/skills/firecrawl/clear.py +4 -8
  148. intentkit/skills/firecrawl/crawl.py +19 -19
  149. intentkit/skills/firecrawl/query.py +4 -3
  150. intentkit/skills/firecrawl/scrape.py +17 -22
  151. intentkit/skills/firecrawl/utils.py +50 -42
  152. intentkit/skills/github/__init__.py +2 -7
  153. intentkit/skills/github/base.py +1 -7
  154. intentkit/skills/github/github_search.py +1 -2
  155. intentkit/skills/heurist/__init__.py +8 -27
  156. intentkit/skills/heurist/base.py +4 -9
  157. intentkit/skills/heurist/image_generation_animagine_xl.py +12 -13
  158. intentkit/skills/heurist/image_generation_arthemy_comics.py +12 -13
  159. intentkit/skills/heurist/image_generation_arthemy_real.py +12 -13
  160. intentkit/skills/heurist/image_generation_braindance.py +12 -13
  161. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +12 -13
  162. intentkit/skills/heurist/image_generation_flux_1_dev.py +12 -13
  163. intentkit/skills/heurist/image_generation_sdxl.py +12 -13
  164. intentkit/skills/http/__init__.py +4 -15
  165. intentkit/skills/http/base.py +1 -7
  166. intentkit/skills/http/get.py +21 -16
  167. intentkit/skills/http/post.py +23 -18
  168. intentkit/skills/http/put.py +23 -18
  169. intentkit/skills/lifi/__init__.py +5 -10
  170. intentkit/skills/lifi/base.py +1 -7
  171. intentkit/skills/lifi/token_execute.py +14 -17
  172. intentkit/skills/lifi/token_quote.py +7 -9
  173. intentkit/skills/lifi/utils.py +16 -16
  174. intentkit/skills/moralis/__init__.py +6 -10
  175. intentkit/skills/moralis/api.py +6 -7
  176. intentkit/skills/moralis/base.py +5 -10
  177. intentkit/skills/moralis/fetch_chain_portfolio.py +10 -11
  178. intentkit/skills/moralis/fetch_nft_portfolio.py +22 -22
  179. intentkit/skills/moralis/fetch_solana_portfolio.py +11 -12
  180. intentkit/skills/moralis/fetch_wallet_portfolio.py +8 -9
  181. intentkit/skills/morpho/__init__.py +5 -9
  182. intentkit/skills/nation/__init__.py +4 -9
  183. intentkit/skills/nation/base.py +5 -10
  184. intentkit/skills/nation/nft_check.py +3 -4
  185. intentkit/skills/onchain.py +23 -0
  186. intentkit/skills/openai/__init__.py +17 -18
  187. intentkit/skills/openai/base.py +10 -14
  188. intentkit/skills/openai/dalle_image_generation.py +3 -8
  189. intentkit/skills/openai/gpt_avatar_generator.py +102 -0
  190. intentkit/skills/openai/gpt_image_generation.py +4 -8
  191. intentkit/skills/openai/gpt_image_mini_generator.py +91 -0
  192. intentkit/skills/openai/gpt_image_to_image.py +4 -8
  193. intentkit/skills/openai/image_to_text.py +3 -7
  194. intentkit/skills/openai/schema.json +32 -0
  195. intentkit/skills/portfolio/__init__.py +11 -35
  196. intentkit/skills/portfolio/base.py +33 -19
  197. intentkit/skills/portfolio/token_balances.py +21 -21
  198. intentkit/skills/portfolio/wallet_approvals.py +17 -18
  199. intentkit/skills/portfolio/wallet_defi_positions.py +3 -3
  200. intentkit/skills/portfolio/wallet_history.py +31 -31
  201. intentkit/skills/portfolio/wallet_net_worth.py +13 -13
  202. intentkit/skills/portfolio/wallet_nfts.py +19 -19
  203. intentkit/skills/portfolio/wallet_profitability.py +18 -18
  204. intentkit/skills/portfolio/wallet_profitability_summary.py +5 -5
  205. intentkit/skills/portfolio/wallet_stats.py +3 -3
  206. intentkit/skills/portfolio/wallet_swaps.py +19 -19
  207. intentkit/skills/pyth/__init__.py +4 -10
  208. intentkit/skills/skills.toml +4 -0
  209. intentkit/skills/slack/__init__.py +5 -17
  210. intentkit/skills/slack/base.py +3 -9
  211. intentkit/skills/slack/get_channel.py +8 -8
  212. intentkit/skills/slack/get_message.py +9 -9
  213. intentkit/skills/slack/schedule_message.py +5 -5
  214. intentkit/skills/slack/send_message.py +3 -5
  215. intentkit/skills/supabase/__init__.py +7 -23
  216. intentkit/skills/supabase/base.py +1 -7
  217. intentkit/skills/supabase/delete_data.py +4 -4
  218. intentkit/skills/supabase/fetch_data.py +12 -12
  219. intentkit/skills/supabase/insert_data.py +4 -4
  220. intentkit/skills/supabase/invoke_function.py +6 -6
  221. intentkit/skills/supabase/update_data.py +6 -6
  222. intentkit/skills/supabase/upsert_data.py +4 -4
  223. intentkit/skills/superfluid/__init__.py +5 -9
  224. intentkit/skills/system/__init__.py +7 -24
  225. intentkit/skills/system/add_autonomous_task.py +10 -12
  226. intentkit/skills/system/delete_autonomous_task.py +2 -2
  227. intentkit/skills/system/edit_autonomous_task.py +14 -18
  228. intentkit/skills/system/list_autonomous_tasks.py +3 -5
  229. intentkit/skills/system/read_agent_api_key.py +6 -4
  230. intentkit/skills/system/regenerate_agent_api_key.py +6 -4
  231. intentkit/skills/tavily/__init__.py +3 -12
  232. intentkit/skills/tavily/base.py +4 -9
  233. intentkit/skills/tavily/tavily_extract.py +2 -4
  234. intentkit/skills/tavily/tavily_search.py +4 -6
  235. intentkit/skills/token/__init__.py +5 -10
  236. intentkit/skills/token/base.py +7 -11
  237. intentkit/skills/token/erc20_transfers.py +19 -19
  238. intentkit/skills/token/token_analytics.py +3 -3
  239. intentkit/skills/token/token_price.py +13 -13
  240. intentkit/skills/token/token_search.py +9 -9
  241. intentkit/skills/twitter/__init__.py +11 -35
  242. intentkit/skills/twitter/base.py +22 -34
  243. intentkit/skills/twitter/follow_user.py +2 -6
  244. intentkit/skills/twitter/get_mentions.py +5 -12
  245. intentkit/skills/twitter/get_timeline.py +4 -12
  246. intentkit/skills/twitter/get_user_by_username.py +2 -6
  247. intentkit/skills/twitter/get_user_tweets.py +5 -13
  248. intentkit/skills/twitter/like_tweet.py +2 -6
  249. intentkit/skills/twitter/post_tweet.py +6 -9
  250. intentkit/skills/twitter/reply_tweet.py +6 -9
  251. intentkit/skills/twitter/retweet.py +2 -6
  252. intentkit/skills/twitter/search_tweets.py +4 -12
  253. intentkit/skills/unrealspeech/__init__.py +2 -7
  254. intentkit/skills/unrealspeech/base.py +2 -8
  255. intentkit/skills/unrealspeech/text_to_speech.py +8 -8
  256. intentkit/skills/venice_audio/__init__.py +98 -106
  257. intentkit/skills/venice_audio/base.py +117 -121
  258. intentkit/skills/venice_audio/input.py +41 -41
  259. intentkit/skills/venice_audio/venice_audio.py +7 -11
  260. intentkit/skills/venice_image/__init__.py +147 -154
  261. intentkit/skills/venice_image/api.py +138 -138
  262. intentkit/skills/venice_image/base.py +185 -192
  263. intentkit/skills/venice_image/config.py +33 -35
  264. intentkit/skills/venice_image/image_enhance/image_enhance.py +2 -3
  265. intentkit/skills/venice_image/image_enhance/image_enhance_base.py +21 -23
  266. intentkit/skills/venice_image/image_enhance/image_enhance_input.py +38 -40
  267. intentkit/skills/venice_image/image_generation/image_generation_base.py +9 -9
  268. intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -26
  269. intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -27
  270. intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -26
  271. intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -158
  272. intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -26
  273. intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -26
  274. intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -28
  275. intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -28
  276. intentkit/skills/venice_image/image_upscale/image_upscale.py +3 -3
  277. intentkit/skills/venice_image/image_upscale/image_upscale_base.py +21 -23
  278. intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -22
  279. intentkit/skills/venice_image/image_vision/image_vision.py +2 -2
  280. intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -17
  281. intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -9
  282. intentkit/skills/venice_image/utils.py +77 -78
  283. intentkit/skills/web_scraper/__init__.py +5 -18
  284. intentkit/skills/web_scraper/base.py +21 -7
  285. intentkit/skills/web_scraper/document_indexer.py +7 -6
  286. intentkit/skills/web_scraper/scrape_and_index.py +15 -15
  287. intentkit/skills/web_scraper/utils.py +62 -63
  288. intentkit/skills/web_scraper/website_indexer.py +17 -19
  289. intentkit/skills/weth/__init__.py +5 -11
  290. intentkit/skills/wow/__init__.py +5 -11
  291. intentkit/skills/x402/__init__.py +61 -0
  292. intentkit/skills/x402/ask_agent.py +98 -0
  293. intentkit/skills/x402/base.py +99 -0
  294. intentkit/skills/x402/http_request.py +117 -0
  295. intentkit/skills/x402/schema.json +45 -0
  296. intentkit/skills/x402/x402.webp +0 -0
  297. intentkit/skills/xmtp/__init__.py +4 -15
  298. intentkit/skills/xmtp/base.py +5 -5
  299. intentkit/skills/xmtp/price.py +6 -6
  300. intentkit/skills/xmtp/swap.py +6 -8
  301. intentkit/skills/xmtp/transfer.py +4 -6
  302. intentkit/utils/error.py +2 -2
  303. intentkit/utils/logging.py +2 -4
  304. intentkit/utils/s3.py +8 -9
  305. intentkit/utils/schema.py +100 -0
  306. intentkit/utils/slack_alert.py +7 -8
  307. {intentkit-0.8.6.dev2.dist-info → intentkit-0.8.17.dist-info}/METADATA +3 -4
  308. intentkit-0.8.17.dist-info/RECORD +466 -0
  309. intentkit/models/generator.py +0 -347
  310. intentkit-0.8.6.dev2.dist-info/RECORD +0 -457
  311. {intentkit-0.8.6.dev2.dist-info → intentkit-0.8.17.dist-info}/WHEEL +0 -0
  312. {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, Optional, Type
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: Optional[SortBy] = Field(
38
- default=SortBy.LIQUIDITY,
39
- description="Sort preference for the results. Options: 'liquidity' (default) or 'volume'",
40
- )
41
- volume_timeframe: Optional[VolumeTimeframe] = 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: Optional[SortBy] = SortBy.LIQUIDITY,
68
- volume_timeframe: Optional[VolumeTimeframe] = 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.user_rate_limit_by_category(
76
- # using hardcoded user_id to make sure it limit across all users
77
- user_id=f"{self.category}{self.name}",
78
- limit=300,
79
- minutes=1,
80
- )
81
-
82
- sort_by = sort_by or SortBy.LIQUIDITY
83
- volume_timeframe = volume_timeframe or VolumeTimeframe.TWENTY_FOUR_HOUR
84
-
85
- # Determine query type
86
- query_type = determine_query_type(query)
87
-
88
- # Process query based on type
89
- if query_type == QueryType.TICKER:
90
- search_query = query[1:] # Remove the '$' prefix
91
- target_ticker = search_query.upper()
92
- else:
93
- search_query = query
94
- target_ticker = None
95
-
96
- logger.info(
97
- f"Executing DexScreener search_token tool with query: '{query}' "
98
- f"(interpreted as {query_type.value} search for '{search_query}'), "
99
- f"sort_by: {sort_by}"
100
- )
101
-
102
- try:
103
- data, error_details = await self._get(
104
- path=API_ENDPOINTS["search"], params={"q": search_query}
105
- )
106
-
107
- if error_details:
108
- return await self._handle_error_response(error_details)
109
- if not data:
110
- logger.error(f"No data or error details returned for query '{query}'")
111
- return create_error_response(
112
- error_type="empty_success",
113
- message="API call returned empty success response.",
114
- additional_data={"query": query},
115
- )
116
-
117
- try:
118
- result = SearchTokenResponseModel.model_validate(data)
119
- except ValidationError as e:
120
- return handle_validation_error(e, query, len(str(data)))
121
-
122
- if not result.pairs:
123
- return create_no_results_response(
124
- query, reason="returned null or empty for pairs"
125
- )
126
-
127
- pairs_list = [p for p in result.pairs if p is not None]
128
-
129
- # Apply filtering based on query type
130
- if query_type == QueryType.TICKER and target_ticker:
131
- pairs_list = filter_ticker_pairs(pairs_list, target_ticker)
132
- if not pairs_list:
133
- return create_no_results_response(
134
- query, reason=f"no match for ticker '${target_ticker}'"
135
- )
136
- elif query_type == QueryType.ADDRESS:
137
- pairs_list = filter_address_pairs(pairs_list, search_query)
138
- if not pairs_list:
139
- return create_no_results_response(
140
- query, reason=f"no match for address '{search_query}'"
141
- )
142
-
143
- # Sort pairs by specified criteria
144
- pairs_list = sort_pairs_by_criteria(pairs_list, sort_by, volume_timeframe)
145
-
146
- # If sorting failed, pairs_list will be returned unchanged by the utility function
147
-
148
- final_count = min(len(pairs_list), MAX_SEARCH_RESULTS)
149
- logger.info(f"Returning {final_count} pairs for query '{query}'")
150
- return format_success_response(
151
- {
152
- **SEARCH_DISCLAIMER,
153
- "pairs": [p.model_dump() for p in pairs_list[:MAX_SEARCH_RESULTS]],
154
- }
155
- )
156
- except Exception as e:
157
- return await self._handle_unexpected_runtime_error(e, query)
158
-
159
- async def _handle_error_response(self, error_details: dict) -> str:
160
- """Formats error details (from _get) into a JSON string."""
161
- if error_details.get("error_type") in [
162
- "connection_error",
163
- "parsing_error",
164
- "unexpected_error",
165
- ]:
166
- logger.error(f"DexScreener tool encountered an error: {error_details}")
167
- else: # api_error
168
- logger.warning(f"DexScreener API returned an error: {error_details}")
169
-
170
- # Truncate potentially large fields before returning to user/LLM
171
- truncated_details = truncate_large_fields(error_details)
172
- return format_success_response(truncated_details)
173
-
174
- async def _handle_unexpected_runtime_error(self, e: Exception, query: str) -> str:
175
- """Formats unexpected runtime exception details into a JSON string."""
176
- logger.exception(
177
- f"An unexpected runtime error occurred in search_token tool _arun method for query '{query}': {e}"
178
- )
179
- return create_error_response(
180
- error_type="runtime_error",
181
- message="An unexpected internal error occurred processing the search request",
182
- details=str(e),
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, Callable, Dict, List, Optional
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: List[PairModel],
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
- ) -> List[PairModel]:
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: List[PairModel], target_ticker: str) -> List[PairModel]:
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: List[PairModel], target_address: str
226
- ) -> List[PairModel]:
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: Optional[str] = None,
260
- additional_data: Optional[Dict[str, Any]] = None,
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: Optional[Dict[str, Any]] = None,
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: Optional[int] = None
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: Dict[str, Any], max_length: int = 500
346
- ) -> Dict[str, Any]:
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: List[PairModel]) -> Dict[str, List[PairModel]]:
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: Dict[str, Any]) -> str:
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 Dict, List, Optional, TypedDict
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: Dict[str, DuneBaseTool] = {}
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
- ) -> List[DuneBaseTool]:
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, store)
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, store: SkillStoreABC) -> Optional[DuneBaseTool]:
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(skill_store=store)
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(skill_store=store)
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 typing import Type
7
-
8
- from langchain.tools.base import ToolException
9
- from pydantic import BaseModel, Field
10
-
11
- from intentkit.abstracts.skill import SkillStoreABC
12
- from intentkit.skills.base import IntentKitSkill
13
-
14
-
15
- class DuneBaseTool(IntentKitSkill):
16
- """Base class for Dune Analytics skills.
17
-
18
- Offers common functionality like API key retrieval and Dune API interaction.
19
- """
20
-
21
- name: str = Field(description="Tool name")
22
- description: str = Field(description="Tool description")
23
- args_schema: Type[BaseModel]
24
- skill_store: SkillStoreABC = Field(description="Skill store for data persistence")
25
-
26
- def get_api_key(self) -> str:
27
- """Retrieve the Dune Analytics API key from context.
28
-
29
- Returns:
30
- API key string.
31
-
32
- Raises:
33
- ToolException: If the API key is not found.
34
- """
35
- context = self.get_context()
36
- skill_config = context.agent.skill_config(self.category)
37
- api_key_provider = skill_config.get("api_key_provider")
38
- if api_key_provider == "agent_owner":
39
- api_key = skill_config.get("api_key")
40
- if api_key:
41
- return api_key
42
- else:
43
- raise ToolException("No api_key found in agent_owner configuration")
44
- else:
45
- raise ToolException(
46
- f"Invalid API key provider: {api_key_provider}. Only 'agent_owner' is supported for Dune Analytics."
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"