intentkit 0.8.16.dev1__py3-none-any.whl → 0.8.17.dev2__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 (274) 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 +6 -5
  5. intentkit/abstracts/skill.py +5 -5
  6. intentkit/abstracts/twitter.py +4 -5
  7. intentkit/clients/cdp.py +19 -77
  8. intentkit/clients/twitter.py +26 -34
  9. intentkit/clients/web3.py +1 -3
  10. intentkit/config/config.py +4 -0
  11. intentkit/core/agent.py +15 -15
  12. intentkit/core/asset.py +1 -2
  13. intentkit/core/client.py +1 -1
  14. intentkit/core/credit.py +19 -20
  15. intentkit/core/engine.py +2 -4
  16. intentkit/core/node.py +2 -1
  17. intentkit/core/prompt.py +3 -4
  18. intentkit/core/scheduler.py +1 -1
  19. intentkit/core/statistics.py +6 -7
  20. intentkit/models/agent.py +125 -92
  21. intentkit/models/agent_data.py +62 -36
  22. intentkit/models/app_setting.py +6 -6
  23. intentkit/models/chat.py +27 -24
  24. intentkit/models/conversation.py +8 -8
  25. intentkit/models/credit.py +62 -64
  26. intentkit/models/db.py +8 -7
  27. intentkit/models/db_mig.py +2 -2
  28. intentkit/models/llm.py +12 -14
  29. intentkit/models/redis.py +2 -3
  30. intentkit/models/skill.py +25 -27
  31. intentkit/models/skills.csv +29 -28
  32. intentkit/models/user.py +21 -22
  33. intentkit/skills/acolyt/ask.py +3 -4
  34. intentkit/skills/acolyt/base.py +1 -3
  35. intentkit/skills/aixbt/base.py +1 -3
  36. intentkit/skills/aixbt/projects.py +13 -13
  37. intentkit/skills/allora/base.py +1 -3
  38. intentkit/skills/allora/price.py +2 -3
  39. intentkit/skills/base.py +15 -22
  40. intentkit/skills/basename/__init__.py +3 -5
  41. intentkit/skills/carv/__init__.py +7 -8
  42. intentkit/skills/carv/base.py +6 -6
  43. intentkit/skills/carv/fetch_news.py +3 -3
  44. intentkit/skills/carv/onchain_query.py +4 -4
  45. intentkit/skills/carv/token_info_and_price.py +5 -5
  46. intentkit/skills/casino/base.py +1 -3
  47. intentkit/skills/casino/deck_draw.py +1 -2
  48. intentkit/skills/casino/deck_shuffle.py +1 -2
  49. intentkit/skills/casino/dice_roll.py +1 -2
  50. intentkit/skills/cdp/__init__.py +3 -5
  51. intentkit/skills/cdp/base.py +1 -3
  52. intentkit/skills/chainlist/base.py +1 -3
  53. intentkit/skills/chainlist/chain_lookup.py +18 -18
  54. intentkit/skills/common/base.py +1 -3
  55. intentkit/skills/common/current_time.py +1 -2
  56. intentkit/skills/cookiefun/base.py +1 -2
  57. intentkit/skills/cookiefun/get_account_details.py +7 -7
  58. intentkit/skills/cookiefun/get_account_feed.py +19 -19
  59. intentkit/skills/cookiefun/get_account_smart_followers.py +7 -7
  60. intentkit/skills/cookiefun/get_sectors.py +3 -3
  61. intentkit/skills/cookiefun/search_accounts.py +9 -9
  62. intentkit/skills/cryptocompare/api.py +2 -3
  63. intentkit/skills/cryptocompare/base.py +6 -6
  64. intentkit/skills/cryptocompare/fetch_news.py +3 -4
  65. intentkit/skills/cryptocompare/fetch_price.py +5 -6
  66. intentkit/skills/cryptocompare/fetch_top_exchanges.py +3 -4
  67. intentkit/skills/cryptocompare/fetch_top_market_cap.py +3 -4
  68. intentkit/skills/cryptocompare/fetch_top_volume.py +3 -4
  69. intentkit/skills/cryptocompare/fetch_trading_signals.py +4 -5
  70. intentkit/skills/cryptopanic/__init__.py +4 -4
  71. intentkit/skills/cryptopanic/base.py +1 -3
  72. intentkit/skills/cryptopanic/fetch_crypto_news.py +3 -5
  73. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +3 -3
  74. intentkit/skills/dapplooker/base.py +1 -3
  75. intentkit/skills/dapplooker/dapplooker_token_data.py +7 -7
  76. intentkit/skills/defillama/api.py +6 -9
  77. intentkit/skills/defillama/base.py +5 -6
  78. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +6 -8
  79. intentkit/skills/defillama/coins/fetch_block.py +4 -6
  80. intentkit/skills/defillama/coins/fetch_current_prices.py +6 -8
  81. intentkit/skills/defillama/coins/fetch_first_price.py +5 -7
  82. intentkit/skills/defillama/coins/fetch_historical_prices.py +7 -9
  83. intentkit/skills/defillama/coins/fetch_price_chart.py +7 -9
  84. intentkit/skills/defillama/coins/fetch_price_percentage.py +5 -7
  85. intentkit/skills/defillama/config/chains.py +1 -3
  86. intentkit/skills/defillama/fees/fetch_fees_overview.py +22 -24
  87. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +14 -16
  88. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +6 -8
  89. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +3 -5
  90. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +5 -7
  91. intentkit/skills/defillama/tests/api_integration.test.py +1 -1
  92. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -4
  93. intentkit/skills/defillama/tvl/fetch_chains.py +7 -9
  94. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -4
  95. intentkit/skills/defillama/tvl/fetch_protocol.py +30 -36
  96. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +1 -3
  97. intentkit/skills/defillama/tvl/fetch_protocols.py +35 -43
  98. intentkit/skills/defillama/volumes/fetch_dex_overview.py +40 -46
  99. intentkit/skills/defillama/volumes/fetch_dex_summary.py +33 -35
  100. intentkit/skills/defillama/volumes/fetch_options_overview.py +22 -26
  101. intentkit/skills/defillama/yields/fetch_pool_chart.py +8 -10
  102. intentkit/skills/defillama/yields/fetch_pools.py +24 -28
  103. intentkit/skills/dexscreener/__init__.py +2 -2
  104. intentkit/skills/dexscreener/base.py +3 -3
  105. intentkit/skills/dexscreener/get_pair_info.py +2 -2
  106. intentkit/skills/dexscreener/get_token_pairs.py +2 -2
  107. intentkit/skills/dexscreener/get_tokens_info.py +5 -5
  108. intentkit/skills/dexscreener/model/search_token_response.py +80 -82
  109. intentkit/skills/dexscreener/search_token.py +182 -182
  110. intentkit/skills/dexscreener/utils.py +15 -14
  111. intentkit/skills/dune_analytics/__init__.py +4 -4
  112. intentkit/skills/dune_analytics/base.py +1 -3
  113. intentkit/skills/dune_analytics/fetch_kol_buys.py +4 -4
  114. intentkit/skills/dune_analytics/fetch_nation_metrics.py +5 -5
  115. intentkit/skills/elfa/base.py +1 -3
  116. intentkit/skills/elfa/mention.py +19 -21
  117. intentkit/skills/elfa/stats.py +4 -4
  118. intentkit/skills/elfa/tokens.py +12 -12
  119. intentkit/skills/elfa/utils.py +25 -27
  120. intentkit/skills/enso/__init__.py +2 -2
  121. intentkit/skills/enso/base.py +5 -8
  122. intentkit/skills/enso/best_yield.py +4 -6
  123. intentkit/skills/enso/networks.py +1 -2
  124. intentkit/skills/enso/prices.py +1 -3
  125. intentkit/skills/enso/route.py +1 -3
  126. intentkit/skills/enso/tokens.py +1 -3
  127. intentkit/skills/enso/wallet.py +5 -5
  128. intentkit/skills/erc20/__init__.py +4 -6
  129. intentkit/skills/erc721/__init__.py +4 -6
  130. intentkit/skills/firecrawl/base.py +1 -3
  131. intentkit/skills/firecrawl/clear.py +1 -2
  132. intentkit/skills/firecrawl/crawl.py +9 -10
  133. intentkit/skills/firecrawl/query.py +1 -2
  134. intentkit/skills/firecrawl/scrape.py +7 -8
  135. intentkit/skills/firecrawl/utils.py +13 -13
  136. intentkit/skills/github/base.py +1 -3
  137. intentkit/skills/github/github_search.py +1 -2
  138. intentkit/skills/heurist/base.py +1 -3
  139. intentkit/skills/heurist/image_generation_animagine_xl.py +7 -8
  140. intentkit/skills/heurist/image_generation_arthemy_comics.py +7 -8
  141. intentkit/skills/heurist/image_generation_arthemy_real.py +7 -8
  142. intentkit/skills/heurist/image_generation_braindance.py +7 -8
  143. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +7 -8
  144. intentkit/skills/heurist/image_generation_flux_1_dev.py +7 -8
  145. intentkit/skills/heurist/image_generation_sdxl.py +7 -8
  146. intentkit/skills/http/base.py +1 -3
  147. intentkit/skills/http/get.py +7 -7
  148. intentkit/skills/http/post.py +9 -9
  149. intentkit/skills/http/put.py +9 -9
  150. intentkit/skills/lifi/__init__.py +4 -4
  151. intentkit/skills/lifi/base.py +1 -3
  152. intentkit/skills/lifi/token_execute.py +13 -13
  153. intentkit/skills/lifi/token_quote.py +6 -6
  154. intentkit/skills/lifi/utils.py +16 -16
  155. intentkit/skills/moralis/__init__.py +3 -3
  156. intentkit/skills/moralis/api.py +6 -7
  157. intentkit/skills/moralis/base.py +2 -4
  158. intentkit/skills/moralis/fetch_chain_portfolio.py +10 -11
  159. intentkit/skills/moralis/fetch_nft_portfolio.py +22 -22
  160. intentkit/skills/moralis/fetch_solana_portfolio.py +11 -12
  161. intentkit/skills/moralis/fetch_wallet_portfolio.py +8 -9
  162. intentkit/skills/morpho/__init__.py +4 -6
  163. intentkit/skills/nation/__init__.py +2 -2
  164. intentkit/skills/nation/base.py +1 -3
  165. intentkit/skills/nation/nft_check.py +3 -4
  166. intentkit/skills/onchain.py +2 -6
  167. intentkit/skills/openai/base.py +1 -3
  168. intentkit/skills/openai/dalle_image_generation.py +1 -3
  169. intentkit/skills/openai/gpt_image_generation.py +2 -3
  170. intentkit/skills/openai/gpt_image_to_image.py +2 -3
  171. intentkit/skills/openai/image_to_text.py +1 -2
  172. intentkit/skills/portfolio/base.py +6 -6
  173. intentkit/skills/portfolio/token_balances.py +21 -21
  174. intentkit/skills/portfolio/wallet_approvals.py +7 -7
  175. intentkit/skills/portfolio/wallet_defi_positions.py +3 -3
  176. intentkit/skills/portfolio/wallet_history.py +21 -21
  177. intentkit/skills/portfolio/wallet_net_worth.py +13 -13
  178. intentkit/skills/portfolio/wallet_nfts.py +19 -19
  179. intentkit/skills/portfolio/wallet_profitability.py +7 -7
  180. intentkit/skills/portfolio/wallet_profitability_summary.py +5 -5
  181. intentkit/skills/portfolio/wallet_stats.py +3 -3
  182. intentkit/skills/portfolio/wallet_swaps.py +19 -19
  183. intentkit/skills/pyth/__init__.py +3 -5
  184. intentkit/skills/slack/base.py +2 -4
  185. intentkit/skills/slack/get_channel.py +8 -8
  186. intentkit/skills/slack/get_message.py +9 -9
  187. intentkit/skills/slack/schedule_message.py +5 -5
  188. intentkit/skills/slack/send_message.py +3 -5
  189. intentkit/skills/supabase/base.py +1 -3
  190. intentkit/skills/supabase/delete_data.py +4 -4
  191. intentkit/skills/supabase/fetch_data.py +12 -12
  192. intentkit/skills/supabase/insert_data.py +4 -4
  193. intentkit/skills/supabase/invoke_function.py +6 -6
  194. intentkit/skills/supabase/update_data.py +6 -6
  195. intentkit/skills/supabase/upsert_data.py +4 -4
  196. intentkit/skills/superfluid/__init__.py +4 -6
  197. intentkit/skills/system/add_autonomous_task.py +8 -10
  198. intentkit/skills/system/edit_autonomous_task.py +12 -14
  199. intentkit/skills/system/list_autonomous_tasks.py +1 -3
  200. intentkit/skills/tavily/base.py +1 -3
  201. intentkit/skills/tavily/tavily_extract.py +1 -2
  202. intentkit/skills/tavily/tavily_search.py +1 -3
  203. intentkit/skills/token/base.py +5 -5
  204. intentkit/skills/token/erc20_transfers.py +19 -19
  205. intentkit/skills/token/token_analytics.py +3 -3
  206. intentkit/skills/token/token_price.py +13 -13
  207. intentkit/skills/token/token_search.py +9 -9
  208. intentkit/skills/twitter/base.py +3 -4
  209. intentkit/skills/twitter/follow_user.py +1 -2
  210. intentkit/skills/twitter/get_mentions.py +3 -4
  211. intentkit/skills/twitter/get_timeline.py +1 -2
  212. intentkit/skills/twitter/get_user_by_username.py +1 -2
  213. intentkit/skills/twitter/get_user_tweets.py +2 -3
  214. intentkit/skills/twitter/like_tweet.py +1 -2
  215. intentkit/skills/twitter/post_tweet.py +3 -4
  216. intentkit/skills/twitter/reply_tweet.py +3 -4
  217. intentkit/skills/twitter/retweet.py +1 -2
  218. intentkit/skills/twitter/search_tweets.py +1 -2
  219. intentkit/skills/unrealspeech/base.py +1 -3
  220. intentkit/skills/unrealspeech/text_to_speech.py +8 -8
  221. intentkit/skills/venice_audio/__init__.py +8 -9
  222. intentkit/skills/venice_audio/base.py +3 -4
  223. intentkit/skills/venice_audio/input.py +41 -41
  224. intentkit/skills/venice_audio/venice_audio.py +6 -6
  225. intentkit/skills/venice_image/__init__.py +5 -5
  226. intentkit/skills/venice_image/api.py +138 -138
  227. intentkit/skills/venice_image/base.py +3 -3
  228. intentkit/skills/venice_image/config.py +33 -35
  229. intentkit/skills/venice_image/image_enhance/image_enhance.py +2 -3
  230. intentkit/skills/venice_image/image_enhance/image_enhance_base.py +21 -23
  231. intentkit/skills/venice_image/image_enhance/image_enhance_input.py +38 -40
  232. intentkit/skills/venice_image/image_generation/image_generation_base.py +9 -9
  233. intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -26
  234. intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -27
  235. intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -26
  236. intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -158
  237. intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -26
  238. intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -26
  239. intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -28
  240. intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -28
  241. intentkit/skills/venice_image/image_upscale/image_upscale.py +3 -3
  242. intentkit/skills/venice_image/image_upscale/image_upscale_base.py +21 -23
  243. intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -22
  244. intentkit/skills/venice_image/image_vision/image_vision.py +2 -2
  245. intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -17
  246. intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -9
  247. intentkit/skills/venice_image/utils.py +77 -78
  248. intentkit/skills/web_scraper/base.py +1 -3
  249. intentkit/skills/web_scraper/document_indexer.py +1 -2
  250. intentkit/skills/web_scraper/scrape_and_index.py +4 -5
  251. intentkit/skills/web_scraper/utils.py +25 -26
  252. intentkit/skills/web_scraper/website_indexer.py +10 -11
  253. intentkit/skills/weth/__init__.py +4 -6
  254. intentkit/skills/wow/__init__.py +4 -6
  255. intentkit/skills/x402/__init__.py +11 -3
  256. intentkit/skills/x402/ask_agent.py +12 -78
  257. intentkit/skills/x402/base.py +90 -0
  258. intentkit/skills/x402/http_request.py +117 -0
  259. intentkit/skills/x402/schema.json +15 -10
  260. intentkit/skills/xmtp/base.py +3 -3
  261. intentkit/skills/xmtp/price.py +2 -2
  262. intentkit/skills/xmtp/swap.py +2 -4
  263. intentkit/skills/xmtp/transfer.py +4 -6
  264. intentkit/utils/error.py +2 -2
  265. intentkit/utils/logging.py +2 -4
  266. intentkit/utils/s3.py +8 -9
  267. intentkit/utils/schema.py +5 -5
  268. intentkit/utils/slack_alert.py +7 -8
  269. {intentkit-0.8.16.dev1.dist-info → intentkit-0.8.17.dev2.dist-info}/METADATA +3 -4
  270. intentkit-0.8.17.dev2.dist-info/RECORD +464 -0
  271. intentkit/models/generator.py +0 -347
  272. intentkit-0.8.16.dev1.dist-info/RECORD +0 -464
  273. {intentkit-0.8.16.dev1.dist-info → intentkit-0.8.17.dev2.dist-info}/WHEEL +0 -0
  274. {intentkit-0.8.16.dev1.dist-info → intentkit-0.8.17.dev2.dist-info}/licenses/LICENSE +0 -0
@@ -1,182 +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.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
- )
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,7 +4,7 @@ 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
9
  from intentkit.skills.base import SkillConfig, SkillState
10
10
  from intentkit.skills.dune_analytics.base import DuneBaseTool
@@ -12,7 +12,7 @@ from intentkit.skills.dune_analytics.base import DuneBaseTool
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
14
  # Cache for skill instances
15
- _skill_cache: Dict[str, DuneBaseTool] = {}
15
+ _skill_cache: dict[str, DuneBaseTool] = {}
16
16
 
17
17
 
18
18
  class SkillStates(TypedDict):
@@ -33,7 +33,7 @@ async def get_skills(
33
33
  config: Config,
34
34
  is_private: bool,
35
35
  **kwargs,
36
- ) -> List[DuneBaseTool]:
36
+ ) -> list[DuneBaseTool]:
37
37
  """Load Dune Analytics skills based on configuration.
38
38
 
39
39
  Args:
@@ -67,7 +67,7 @@ async def get_skills(
67
67
  return loaded_skills
68
68
 
69
69
 
70
- def get_dune_skill(name: str) -> Optional[DuneBaseTool]:
70
+ def get_dune_skill(name: str) -> DuneBaseTool | None:
71
71
  """Retrieve a Dune Analytics skill instance by name.
72
72
 
73
73
  Args:
@@ -3,8 +3,6 @@
3
3
  Provides shared functionality for interacting with the Dune Analytics API.
4
4
  """
5
5
 
6
- from typing import Type
7
-
8
6
  from langchain_core.tools.base import ToolException
9
7
  from pydantic import BaseModel, Field
10
8
 
@@ -19,7 +17,7 @@ class DuneBaseTool(IntentKitSkill):
19
17
 
20
18
  name: str = Field(description="Tool name")
21
19
  description: str = Field(description="Tool description")
22
- args_schema: Type[BaseModel]
20
+ args_schema: type[BaseModel]
23
21
 
24
22
  def get_api_key(self) -> str:
25
23
  """Retrieve the Dune Analytics API key from context.
@@ -3,7 +3,7 @@
3
3
  Uses query ID 4832844 to retrieve a list of KOL buy transactions.
4
4
  """
5
5
 
6
- from typing import Any, Dict, Type
6
+ from typing import Any
7
7
 
8
8
  import httpx
9
9
  from pydantic import BaseModel, Field
@@ -28,7 +28,7 @@ class KOLBuysInput(BaseModel):
28
28
  class KOLBuyData(BaseModel):
29
29
  """Data model for KOL buy results."""
30
30
 
31
- data: Dict[str, Any] = Field(description="KOL buy data from Dune API")
31
+ data: dict[str, Any] = Field(description="KOL buy data from Dune API")
32
32
  error: str = Field(default="", description="Error message if fetch failed")
33
33
 
34
34
 
@@ -47,14 +47,14 @@ class FetchKOLBuys(DuneBaseTool):
47
47
  "Fetches a list of KOL memecoin buy transactions on Solana from Dune Analytics API using query ID 4832844. "
48
48
  "Supports a configurable limit for the number of results. Handles rate limits with retries."
49
49
  )
50
- args_schema: Type[BaseModel] = KOLBuysInput
50
+ args_schema: type[BaseModel] = KOLBuysInput
51
51
 
52
52
  @retry(
53
53
  stop=stop_after_attempt(3), wait=wait_exponential(multiplier=5, min=5, max=60)
54
54
  )
55
55
  async def fetch_data(
56
56
  self, query_id: int, api_key: str, limit: int = 10
57
- ) -> Dict[str, Any]:
57
+ ) -> dict[str, Any]:
58
58
  """Fetch data for a specific Dune query.
59
59
 
60
60
  Args:
@@ -5,7 +5,7 @@ Supports predefined metrics (e.g., total_users, unique_ai_citizens) or direct qu
5
5
 
6
6
  import difflib
7
7
  import re
8
- from typing import Any, Dict, Type
8
+ from typing import Any
9
9
 
10
10
  import httpx
11
11
  from pydantic import BaseModel, Field
@@ -62,14 +62,14 @@ class MetricData(BaseModel):
62
62
  """Data model for a single metric result."""
63
63
 
64
64
  metric: str = Field(description="Metric name or query ID")
65
- data: Dict[str, Any] = Field(description="Metric data from Dune API")
65
+ data: dict[str, Any] = Field(description="Metric data from Dune API")
66
66
  error: str = Field(default="", description="Error message if fetch failed")
67
67
 
68
68
 
69
69
  class NationMetricsOutput(BaseModel):
70
70
  """Output schema for Crestal Nation metrics."""
71
71
 
72
- metrics: Dict[str, MetricData] = Field(
72
+ metrics: dict[str, MetricData] = Field(
73
73
  description="Dictionary of metric names or query IDs to their data"
74
74
  )
75
75
  summary: str = Field(description="Summary of fetched metrics")
@@ -84,7 +84,7 @@ class FetchNationMetrics(DuneBaseTool):
84
84
  "Supports predefined metrics, direct query IDs, or all configured metrics if none specified. "
85
85
  "Handles rate limits with retries."
86
86
  )
87
- args_schema: Type[BaseModel] = NationMetricsInput
87
+ args_schema: type[BaseModel] = NationMetricsInput
88
88
 
89
89
  def normalize_metric(self, metric: str) -> str:
90
90
  """Normalize a metric string for matching.
@@ -122,7 +122,7 @@ class FetchNationMetrics(DuneBaseTool):
122
122
  )
123
123
  async def fetch_data(
124
124
  self, query_id: int, api_key: str, limit: int = 1000
125
- ) -> Dict[str, Any]:
125
+ ) -> dict[str, Any]:
126
126
  """Fetch data for a specific Dune query.
127
127
 
128
128
  Args:
@@ -1,5 +1,3 @@
1
- from typing import Type
2
-
3
1
  from langchain_core.tools.base import ToolException
4
2
  from pydantic import BaseModel, Field
5
3
 
@@ -14,7 +12,7 @@ class ElfaBaseTool(IntentKitSkill):
14
12
 
15
13
  name: str = Field(description="The name of the tool")
16
14
  description: str = Field(description="A description of what the tool does")
17
- args_schema: Type[BaseModel]
15
+ args_schema: type[BaseModel]
18
16
 
19
17
  def get_api_key(self) -> str:
20
18
  context = self.get_context()