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,130 +1,125 @@
1
- import json
2
- import logging
3
- from typing import Any, Dict, Optional, Tuple
4
-
5
- import httpx
6
- from pydantic import Field
7
-
8
- from intentkit.abstracts.skill import SkillStoreABC
9
- from intentkit.skills.base import IntentKitSkill
10
- from intentkit.skills.dexscreener.utils import DEXSCREENER_BASE_URL
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
- # ApiResult still represents (success_data, error_data)
15
- ApiResult = Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]
16
-
17
-
18
- class DexScreenerBaseTool(IntentKitSkill):
19
- """
20
- Generic base class for tools interacting with the Dex Screener API.
21
- Handles shared logic like API calls and error reporting via return values.
22
- """
23
-
24
- skill_store: SkillStoreABC = Field(
25
- description="The skill store for persisting data and configs."
26
- )
27
- base_url: str = DEXSCREENER_BASE_URL
28
-
29
- @property
30
- def category(self) -> str:
31
- return "dexscreener"
32
-
33
- async def _get(
34
- self,
35
- path: str,
36
- params: Optional[Dict[str, Any]] = None,
37
- ) -> ApiResult:
38
- """
39
- Makes an asynchronous GET request to the DexScreener API.
40
-
41
- Args:
42
- path: The API endpoint path (e.g., "/dex/search").
43
- params: Optional dictionary of query parameters.
44
-
45
- Returns:
46
- A tuple (data, error_details):
47
- - (dict, None): On HTTP 2xx success with valid JSON response.
48
- - (None, dict): On any error (API error, connection error,
49
- JSON parsing error, unexpected error). The dict
50
- contains details including an 'error_type'.
51
- """
52
- if not path.startswith("/"):
53
- path = "/" + path
54
-
55
- url = f"{self.base_url}{path}"
56
- headers = {"Accept": "application/json"}
57
- method = "GET"
58
-
59
- logger.debug(f"Calling DexScreener API: {method} {url} with params: {params}")
60
- response = None # Define response outside try block for access in except
61
-
62
- try:
63
- async with httpx.AsyncClient() as client:
64
- response = await client.request(
65
- method, url, params=params, headers=headers
66
- )
67
-
68
- # Attempt to parse JSON response text
69
- try:
70
- response_data = response.json()
71
- except json.JSONDecodeError as json_err:
72
- logger.error(
73
- f"Failed to parse JSON response from {url}. Status: {response.status_code}. Response text: {response.text}",
74
- exc_info=True,
75
- )
76
- error_details = {
77
- "error": "Failed to parse DexScreener API response",
78
- "error_type": "parsing_error",
79
- "status_code": response.status_code,
80
- "details": response.text, # Raw text causing the error
81
- "original_exception": str(json_err),
82
- "url": url,
83
- }
84
- return None, error_details # Return parsing error
85
-
86
- # Check HTTP status *after* attempting JSON parse
87
- if response.is_success: # 2xx
88
- logger.debug(
89
- f"DexScreener API success response status: {response.status_code}"
90
- )
91
- return response_data, None # Success
92
- else: # 4xx/5xx
93
- logger.warning(
94
- f"DexScreener API returned error status: {response.status_code} - {response.text}"
95
- )
96
- error_details = {
97
- "error": "DexScreener API request failed",
98
- "error_type": "api_error",
99
- "status_code": response.status_code,
100
- "response_body": response_data, # Parsed error body if available
101
- "url": url,
102
- }
103
- return None, error_details # Return API error
104
-
105
- except httpx.RequestError as req_err:
106
- logger.error(
107
- f"Request error connecting to DexScreener API: {req_err}", exc_info=True
108
- )
109
- error_details = {
110
- "error": "Failed to connect to DexScreener API",
111
- "error_type": "connection_error",
112
- "details": str(req_err),
113
- "url": url,
114
- }
115
- return None, error_details # Return connection error
116
-
117
- except Exception as e:
118
- # Catch any other unexpected errors during the process
119
- logger.exception(
120
- f"An unexpected error occurred during DexScreener API GET call: {e}"
121
- )
122
- status_code = response.status_code if response else None
123
- error_details = {
124
- "error": "An unexpected error occurred during API call",
125
- "error_type": "unexpected_error",
126
- "status_code": status_code, # Include if available
127
- "details": str(e),
128
- "url": url,
129
- }
130
- return None, error_details # Return unexpected error
1
+ import json
2
+ import logging
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from intentkit.skills.base import IntentKitSkill
8
+ from intentkit.skills.dexscreener.utils import DEXSCREENER_BASE_URL
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # ApiResult still represents (success_data, error_data)
13
+ ApiResult = tuple[dict[str, Any] | None, dict[str, Any] | None]
14
+
15
+
16
+ class DexScreenerBaseTool(IntentKitSkill):
17
+ """
18
+ Generic base class for tools interacting with the Dex Screener API.
19
+ Handles shared logic like API calls and error reporting via return values.
20
+ """
21
+
22
+ base_url: str = DEXSCREENER_BASE_URL
23
+
24
+ @property
25
+ def category(self) -> str:
26
+ return "dexscreener"
27
+
28
+ async def _get(
29
+ self,
30
+ path: str,
31
+ params: dict[str, Any] | None = None,
32
+ ) -> ApiResult:
33
+ """
34
+ Makes an asynchronous GET request to the DexScreener API.
35
+
36
+ Args:
37
+ path: The API endpoint path (e.g., "/dex/search").
38
+ params: Optional dictionary of query parameters.
39
+
40
+ Returns:
41
+ A tuple (data, error_details):
42
+ - (dict, None): On HTTP 2xx success with valid JSON response.
43
+ - (None, dict): On any error (API error, connection error,
44
+ JSON parsing error, unexpected error). The dict
45
+ contains details including an 'error_type'.
46
+ """
47
+ if not path.startswith("/"):
48
+ path = "/" + path
49
+
50
+ url = f"{self.base_url}{path}"
51
+ headers = {"Accept": "application/json"}
52
+ method = "GET"
53
+
54
+ logger.debug(f"Calling DexScreener API: {method} {url} with params: {params}")
55
+ response = None # Define response outside try block for access in except
56
+
57
+ try:
58
+ async with httpx.AsyncClient() as client:
59
+ response = await client.request(
60
+ method, url, params=params, headers=headers
61
+ )
62
+
63
+ # Attempt to parse JSON response text
64
+ try:
65
+ response_data = response.json()
66
+ except json.JSONDecodeError as json_err:
67
+ logger.error(
68
+ f"Failed to parse JSON response from {url}. Status: {response.status_code}. Response text: {response.text}",
69
+ exc_info=True,
70
+ )
71
+ error_details = {
72
+ "error": "Failed to parse DexScreener API response",
73
+ "error_type": "parsing_error",
74
+ "status_code": response.status_code,
75
+ "details": response.text, # Raw text causing the error
76
+ "original_exception": str(json_err),
77
+ "url": url,
78
+ }
79
+ return None, error_details # Return parsing error
80
+
81
+ # Check HTTP status *after* attempting JSON parse
82
+ if response.is_success: # 2xx
83
+ logger.debug(
84
+ f"DexScreener API success response status: {response.status_code}"
85
+ )
86
+ return response_data, None # Success
87
+ else: # 4xx/5xx
88
+ logger.warning(
89
+ f"DexScreener API returned error status: {response.status_code} - {response.text}"
90
+ )
91
+ error_details = {
92
+ "error": "DexScreener API request failed",
93
+ "error_type": "api_error",
94
+ "status_code": response.status_code,
95
+ "response_body": response_data, # Parsed error body if available
96
+ "url": url,
97
+ }
98
+ return None, error_details # Return API error
99
+
100
+ except httpx.RequestError as req_err:
101
+ logger.error(
102
+ f"Request error connecting to DexScreener API: {req_err}", exc_info=True
103
+ )
104
+ error_details = {
105
+ "error": "Failed to connect to DexScreener API",
106
+ "error_type": "connection_error",
107
+ "details": str(req_err),
108
+ "url": url,
109
+ }
110
+ return None, error_details # Return connection error
111
+
112
+ except Exception as e:
113
+ # Catch any other unexpected errors during the process
114
+ logger.exception(
115
+ f"An unexpected error occurred during DexScreener API GET call: {e}"
116
+ )
117
+ status_code = response.status_code if response else None
118
+ error_details = {
119
+ "error": "An unexpected error occurred during API call",
120
+ "error_type": "unexpected_error",
121
+ "status_code": status_code, # Include if available
122
+ "details": str(e),
123
+ "url": url,
124
+ }
125
+ return None, error_details # Return unexpected error
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Any, Type
2
+ from typing import Any
3
3
 
4
4
  from pydantic import BaseModel, Field
5
5
 
@@ -39,7 +39,7 @@ class GetPairInfo(DexScreenerBaseTool):
39
39
  "market cap, FDV, transaction counts, and social links. "
40
40
  "Use this tool when you have a specific pair address and need detailed trading metrics."
41
41
  )
42
- args_schema: Type[BaseModel] = GetPairInfoInput
42
+ args_schema: type[BaseModel] = GetPairInfoInput
43
43
 
44
44
  async def _arun(
45
45
  self,
@@ -50,10 +50,9 @@ class GetPairInfo(DexScreenerBaseTool):
50
50
  """Implementation to get specific pair information."""
51
51
 
52
52
  # Apply rate limiting
53
- await self.user_rate_limit_by_category(
54
- user_id=f"{self.category}{self.name}",
53
+ await self.global_rate_limit_by_skill(
55
54
  limit=RATE_LIMITS["pairs"],
56
- minutes=1,
55
+ seconds=60,
57
56
  )
58
57
 
59
58
  logger.info(
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Any, Type
2
+ from typing import Any
3
3
 
4
4
  from pydantic import BaseModel, Field, ValidationError
5
5
 
@@ -44,7 +44,7 @@ class GetTokenPairs(DexScreenerBaseTool):
44
44
  "DEX information, liquidity, volume, and pricing data for each pair. "
45
45
  "Use this tool to analyze all available trading venues and liquidity sources for a specific token."
46
46
  )
47
- args_schema: Type[BaseModel] = GetTokenPairsInput
47
+ args_schema: type[BaseModel] = GetTokenPairsInput
48
48
 
49
49
  async def _arun(
50
50
  self,
@@ -55,10 +55,9 @@ class GetTokenPairs(DexScreenerBaseTool):
55
55
  """Implementation to get all pairs for a specific token."""
56
56
 
57
57
  # Apply rate limiting
58
- await self.user_rate_limit_by_category(
59
- user_id=f"{self.category}{self.name}",
58
+ await self.global_rate_limit_by_skill(
60
59
  limit=RATE_LIMITS["token_pairs"],
61
- minutes=1,
60
+ seconds=60,
62
61
  )
63
62
 
64
63
  logger.info(
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Any, List, Type
2
+ from typing import Any
3
3
 
4
4
  from pydantic import BaseModel, Field, ValidationError, field_validator
5
5
 
@@ -29,14 +29,14 @@ class GetTokensInfoInput(BaseModel):
29
29
  chain_id: str = Field(
30
30
  description="The blockchain chain ID (e.g., 'ethereum', 'solana', 'bsc', 'polygon', 'arbitrum', 'base', 'avalanche')"
31
31
  )
32
- token_addresses: List[str] = Field(
32
+ token_addresses: list[str] = Field(
33
33
  description=f"List of token contract addresses to retrieve info for (maximum {MAX_TOKENS_BATCH} addresses). "
34
34
  "Each address should be in the format '0x1234...abcd' for Ethereum-based chains."
35
35
  )
36
36
 
37
37
  @field_validator("token_addresses")
38
38
  @classmethod
39
- def validate_token_addresses(cls, v: List[str]) -> List[str]:
39
+ def validate_token_addresses(cls, v: list[str]) -> list[str]:
40
40
  if not v:
41
41
  raise ValueError("At least one token address is required")
42
42
  if len(v) > MAX_TOKENS_BATCH:
@@ -64,21 +64,20 @@ class GetTokensInfo(DexScreenerBaseTool):
64
64
  "This is more efficient than making individual calls when you need info for multiple tokens. "
65
65
  "Use this tool for portfolio analysis or comparing multiple tokens at once."
66
66
  )
67
- args_schema: Type[BaseModel] = GetTokensInfoInput
67
+ args_schema: type[BaseModel] = GetTokensInfoInput
68
68
 
69
69
  async def _arun(
70
70
  self,
71
71
  chain_id: str,
72
- token_addresses: List[str],
72
+ token_addresses: list[str],
73
73
  **kwargs: Any,
74
74
  ) -> str:
75
75
  """Implementation to get information for multiple tokens."""
76
76
 
77
77
  # Apply rate limiting
78
- await self.user_rate_limit_by_category(
79
- user_id=f"{self.category}{self.name}",
78
+ await self.global_rate_limit_by_skill(
80
79
  limit=RATE_LIMITS["tokens"],
81
- minutes=1,
80
+ seconds=60,
82
81
  )
83
82
 
84
83
  logger.info(
@@ -1,82 +1,80 @@
1
- from typing import List, Optional
2
-
3
- from pydantic import BaseModel
4
-
5
-
6
- class TokenModel(BaseModel):
7
- address: Optional[str] = None
8
- name: Optional[str] = None
9
- symbol: Optional[str] = None
10
-
11
-
12
- class TxnsDetailsModel(BaseModel):
13
- buys: Optional[int] = None
14
- sells: Optional[int] = None
15
-
16
-
17
- class TxnsModel(BaseModel):
18
- m5: Optional[TxnsDetailsModel] = None
19
- h1: Optional[TxnsDetailsModel] = None
20
- h6: Optional[TxnsDetailsModel] = None
21
- h24: Optional[TxnsDetailsModel] = None
22
-
23
-
24
- class VolumeModel(BaseModel):
25
- h24: Optional[float] = None
26
- h6: Optional[float] = None
27
- h1: Optional[float] = None
28
- m5: Optional[float] = None
29
-
30
-
31
- class PriceChangeModel(BaseModel):
32
- m5: Optional[float] = None
33
- h1: Optional[float] = None
34
- h6: Optional[float] = None
35
- h24: Optional[float] = None
36
-
37
-
38
- class LiquidityModel(BaseModel):
39
- usd: Optional[float] = None
40
- base: Optional[float] = None
41
- quote: Optional[float] = None
42
-
43
-
44
- class WebsiteModel(BaseModel):
45
- label: Optional[str] = None
46
- url: Optional[str] = None
47
-
48
-
49
- class SocialModel(BaseModel):
50
- type: Optional[str] = None
51
- url: Optional[str] = None
52
-
53
-
54
- class InfoModel(BaseModel):
55
- imageUrl: Optional[str] = None
56
- websites: Optional[List[Optional[WebsiteModel]]] = None
57
- socials: Optional[List[Optional[SocialModel]]] = None
58
-
59
-
60
- class PairModel(BaseModel):
61
- chainId: Optional[str] = None
62
- dexId: Optional[str] = None
63
- url: Optional[str] = None
64
- pairAddress: Optional[str] = None
65
- labels: Optional[List[Optional[str]]] = None
66
- baseToken: Optional[TokenModel] = None
67
- quoteToken: Optional[TokenModel] = None
68
- priceNative: Optional[str] = None
69
- priceUsd: Optional[str] = None
70
- txns: Optional[TxnsModel] = None
71
- volume: Optional[VolumeModel] = None
72
- priceChange: Optional[PriceChangeModel] = None
73
- liquidity: Optional[LiquidityModel] = None
74
- fdv: Optional[float] = None
75
- marketCap: Optional[float] = None
76
- pairCreatedAt: Optional[int] = None
77
- info: Optional[InfoModel] = None
78
-
79
-
80
- class SearchTokenResponseModel(BaseModel):
81
- schemaVersion: Optional[str] = None
82
- pairs: Optional[List[Optional[PairModel]]] = None
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class TokenModel(BaseModel):
5
+ address: str | None = None
6
+ name: str | None = None
7
+ symbol: str | None = None
8
+
9
+
10
+ class TxnsDetailsModel(BaseModel):
11
+ buys: int | None = None
12
+ sells: int | None = None
13
+
14
+
15
+ class TxnsModel(BaseModel):
16
+ m5: TxnsDetailsModel | None = None
17
+ h1: TxnsDetailsModel | None = None
18
+ h6: TxnsDetailsModel | None = None
19
+ h24: TxnsDetailsModel | None = None
20
+
21
+
22
+ class VolumeModel(BaseModel):
23
+ h24: float | None = None
24
+ h6: float | None = None
25
+ h1: float | None = None
26
+ m5: float | None = None
27
+
28
+
29
+ class PriceChangeModel(BaseModel):
30
+ m5: float | None = None
31
+ h1: float | None = None
32
+ h6: float | None = None
33
+ h24: float | None = None
34
+
35
+
36
+ class LiquidityModel(BaseModel):
37
+ usd: float | None = None
38
+ base: float | None = None
39
+ quote: float | None = None
40
+
41
+
42
+ class WebsiteModel(BaseModel):
43
+ label: str | None = None
44
+ url: str | None = None
45
+
46
+
47
+ class SocialModel(BaseModel):
48
+ type: str | None = None
49
+ url: str | None = None
50
+
51
+
52
+ class InfoModel(BaseModel):
53
+ imageUrl: str | None = None
54
+ websites: list[WebsiteModel | None] | None = None
55
+ socials: list[SocialModel | None] | None = None
56
+
57
+
58
+ class PairModel(BaseModel):
59
+ chainId: str | None = None
60
+ dexId: str | None = None
61
+ url: str | None = None
62
+ pairAddress: str | None = None
63
+ labels: list[str | None] | None = None
64
+ baseToken: TokenModel | None = None
65
+ quoteToken: TokenModel | None = None
66
+ priceNative: str | None = None
67
+ priceUsd: str | None = None
68
+ txns: TxnsModel | None = None
69
+ volume: VolumeModel | None = None
70
+ priceChange: PriceChangeModel | None = None
71
+ liquidity: LiquidityModel | None = None
72
+ fdv: float | None = None
73
+ marketCap: float | None = None
74
+ pairCreatedAt: int | None = None
75
+ info: InfoModel | None = None
76
+
77
+
78
+ class SearchTokenResponseModel(BaseModel):
79
+ schemaVersion: str | None = None
80
+ pairs: list[PairModel | None] | None = None