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,5 +1,4 @@
1
1
  import logging
2
- from typing import List, Type
3
2
  from urllib.parse import urljoin, urlparse
4
3
 
5
4
  import httpx
@@ -44,11 +43,11 @@ class WebsiteIndexerInput(BaseModel):
44
43
  ge=0,
45
44
  le=1000,
46
45
  )
47
- include_patterns: List[str] = Field(
46
+ include_patterns: list[str] = Field(
48
47
  description="URL patterns to include (e.g., ['/blog/', '/docs/']). If empty, all URLs are included",
49
48
  default=[],
50
49
  )
51
- exclude_patterns: List[str] = Field(
50
+ exclude_patterns: list[str] = Field(
52
51
  description="URL patterns to exclude (e.g., ['/admin/', '/private/'])",
53
52
  default=[],
54
53
  )
@@ -68,7 +67,7 @@ class WebsiteIndexer(WebScraperBaseTool):
68
67
  "This tool finds sitemaps from robots.txt, parses the XML content to extract URLs, "
69
68
  "and then uses the reliable scrape_and_index functionality for content indexing."
70
69
  )
71
- args_schema: Type[BaseModel] = WebsiteIndexerInput
70
+ args_schema: type[BaseModel] = WebsiteIndexerInput
72
71
 
73
72
  def _normalize_url(self, url: str) -> str:
74
73
  """Normalize URL by ensuring it has a proper scheme."""
@@ -106,7 +105,7 @@ class WebsiteIndexer(WebScraperBaseTool):
106
105
 
107
106
  def _extract_sitemaps_from_robots(
108
107
  self, robots_content: str, base_url: str
109
- ) -> List[str]:
108
+ ) -> list[str]:
110
109
  """Extract sitemap URLs from robots.txt content."""
111
110
  sitemaps = []
112
111
 
@@ -121,7 +120,7 @@ class WebsiteIndexer(WebScraperBaseTool):
121
120
 
122
121
  return sitemaps
123
122
 
124
- def _get_common_sitemap_patterns(self, base_url: str) -> List[str]:
123
+ def _get_common_sitemap_patterns(self, base_url: str) -> list[str]:
125
124
  """Generate common sitemap URL patterns."""
126
125
  return [
127
126
  urljoin(base_url, "/sitemap.xml"),
@@ -157,7 +156,7 @@ class WebsiteIndexer(WebScraperBaseTool):
157
156
  logger.warning(f"Could not fetch sitemap from {sitemap_url}: {e}")
158
157
  return ""
159
158
 
160
- async def _get_all_sitemap_content(self, base_url: str) -> tuple[str, List[str]]:
159
+ async def _get_all_sitemap_content(self, base_url: str) -> tuple[str, list[str]]:
161
160
  """Get all sitemap content for AI analysis."""
162
161
  all_content = []
163
162
  found_sitemaps = []
@@ -200,7 +199,7 @@ class WebsiteIndexer(WebScraperBaseTool):
200
199
  return combined_xml, found_sitemaps
201
200
 
202
201
  def _create_ai_extraction_prompt(
203
- self, sitemap_xml: str, include_patterns: List[str], exclude_patterns: List[str]
202
+ self, sitemap_xml: str, include_patterns: list[str], exclude_patterns: list[str]
204
203
  ) -> str:
205
204
  """Create a prompt for AI to extract URLs from sitemap XML."""
206
205
  filter_instructions = ""
@@ -225,7 +224,7 @@ INSTRUCTIONS:
225
224
 
226
225
  Extract the URLs now:"""
227
226
 
228
- def _parse_ai_response(self, ai_response: str) -> List[str]:
227
+ def _parse_ai_response(self, ai_response: str) -> list[str]:
229
228
  """Parse AI response to extract clean URLs."""
230
229
  urls = []
231
230
 
@@ -281,8 +280,8 @@ Extract the URLs now:"""
281
280
  max_urls: int = 50,
282
281
  chunk_size: int = DEFAULT_CHUNK_SIZE,
283
282
  chunk_overlap: int = DEFAULT_CHUNK_OVERLAP,
284
- include_patterns: List[str] = None,
285
- exclude_patterns: List[str] = None,
283
+ include_patterns: list[str] = None,
284
+ exclude_patterns: list[str] = None,
286
285
  **kwargs,
287
286
  ) -> str:
288
287
  """Discover website sitemaps, extract URLs with AI, and delegate to scrape_and_index."""
@@ -1,9 +1,10 @@
1
1
  """WETH AgentKit skills."""
2
2
 
3
- from typing import TYPE_CHECKING, Optional, TypedDict
3
+ from typing import TypedDict
4
4
 
5
5
  from coinbase_agentkit import weth_action_provider
6
6
 
7
+ from intentkit.models.agent import Agent
7
8
  from intentkit.skills.base import (
8
9
  SkillConfig,
9
10
  SkillState,
@@ -12,9 +13,6 @@ from intentkit.skills.base import (
12
13
  )
13
14
  from intentkit.skills.weth.base import WethBaseTool
14
15
 
15
- if TYPE_CHECKING:
16
- from intentkit.models.agent import Agent
17
-
18
16
 
19
17
  class SkillStates(TypedDict):
20
18
  WethActionProvider_wrap_eth: SkillState
@@ -27,10 +25,10 @@ class Config(SkillConfig):
27
25
 
28
26
 
29
27
  async def get_skills(
30
- config: "Config",
28
+ config: Config,
31
29
  is_private: bool,
32
30
  agent_id: str,
33
- agent: Optional["Agent"] = None,
31
+ agent: Agent | None = None,
34
32
  **_,
35
33
  ) -> list[WethBaseTool]:
36
34
  """Get all WETH skills."""
@@ -1,9 +1,10 @@
1
1
  """WOW AgentKit skills."""
2
2
 
3
- from typing import TYPE_CHECKING, Optional, TypedDict
3
+ from typing import TypedDict
4
4
 
5
5
  from coinbase_agentkit import wow_action_provider
6
6
 
7
+ from intentkit.models.agent import Agent
7
8
  from intentkit.skills.base import (
8
9
  SkillConfig,
9
10
  SkillState,
@@ -12,9 +13,6 @@ from intentkit.skills.base import (
12
13
  )
13
14
  from intentkit.skills.wow.base import WowBaseTool
14
15
 
15
- if TYPE_CHECKING:
16
- from intentkit.models.agent import Agent
17
-
18
16
 
19
17
  class SkillStates(TypedDict):
20
18
  WowActionProvider_buy_token: SkillState
@@ -29,10 +27,10 @@ class Config(SkillConfig):
29
27
 
30
28
 
31
29
  async def get_skills(
32
- config: "Config",
30
+ config: Config,
33
31
  is_private: bool,
34
32
  agent_id: str,
35
- agent: Optional["Agent"] = None,
33
+ agent: Agent | None = None,
36
34
  **_,
37
35
  ) -> list[WowBaseTool]:
38
36
  """Get all WOW skills."""
@@ -6,6 +6,7 @@ from typing import TypedDict
6
6
  from intentkit.skills.base import SkillConfig, SkillState
7
7
  from intentkit.skills.x402.ask_agent import X402AskAgent
8
8
  from intentkit.skills.x402.base import X402BaseSkill
9
+ from intentkit.skills.x402.http_request import X402HttpRequest
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
@@ -14,6 +15,7 @@ _cache: dict[str, X402BaseSkill] = {}
14
15
 
15
16
  class SkillStates(TypedDict):
16
17
  x402_ask_agent: SkillState
18
+ x402_http_request: SkillState
17
19
 
18
20
 
19
21
  class Config(SkillConfig):
@@ -22,6 +24,12 @@ class Config(SkillConfig):
22
24
  states: SkillStates
23
25
 
24
26
 
27
+ _SKILL_BUILDERS: dict[str, type[X402BaseSkill]] = {
28
+ "x402_ask_agent": X402AskAgent,
29
+ "x402_http_request": X402HttpRequest,
30
+ }
31
+
32
+
25
33
  async def get_skills(
26
34
  config: "Config",
27
35
  is_private: bool,
@@ -44,10 +52,10 @@ async def get_skills(
44
52
 
45
53
 
46
54
  def _get_skill(name: str) -> X402BaseSkill | None:
47
- if name == "x402_ask_agent":
55
+ builder = _SKILL_BUILDERS.get(name)
56
+ if builder:
48
57
  if name not in _cache:
49
- _cache[name] = X402AskAgent()
58
+ _cache[name] = builder()
50
59
  return _cache[name]
51
-
52
60
  logger.warning("Unknown x402 skill requested: %s", name)
53
61
  return None
@@ -1,15 +1,9 @@
1
1
  import logging
2
- import threading
3
- from typing import Any, Dict, Optional, Type
2
+ from typing import Any
4
3
 
5
- from coinbase_agentkit.wallet_providers.evm_wallet_provider import (
6
- EvmWalletSigner as CoinbaseEvmWalletSigner,
7
- )
8
4
  from langchain_core.tools import ToolException
9
5
  from pydantic import BaseModel, Field
10
- from x402.clients.httpx import x402HttpxClient
11
6
 
12
- from intentkit.clients import get_wallet_provider
13
7
  from intentkit.config.config import config
14
8
  from intentkit.models.chat import AuthorType
15
9
  from intentkit.skills.x402.base import X402BaseSkill
@@ -22,10 +16,10 @@ class AskAgentInput(BaseModel):
22
16
 
23
17
  agent_id: str = Field(description="ID or slug of the agent to query.")
24
18
  message: str = Field(description="Message to send to the target agent.")
25
- search_mode: Optional[bool] = Field(
19
+ search_mode: bool | None = Field(
26
20
  default=None, description="Enable search mode when interacting with the agent."
27
21
  )
28
- super_mode: Optional[bool] = Field(
22
+ super_mode: bool | None = Field(
29
23
  default=None, description="Enable super mode when interacting with the agent."
30
24
  )
31
25
 
@@ -37,26 +31,22 @@ class X402AskAgent(X402BaseSkill):
37
31
  description: str = (
38
32
  "Call another agent through the x402 API and return the final agent message."
39
33
  )
40
- args_schema: Type[BaseModel] = AskAgentInput
34
+ args_schema: type[BaseModel] = AskAgentInput
41
35
 
42
36
  async def _arun(
43
37
  self,
44
38
  agent_id: str,
45
39
  message: str,
46
- search_mode: Optional[bool] = None,
47
- super_mode: Optional[bool] = None,
40
+ search_mode: bool | None = None,
41
+ super_mode: bool | None = None,
48
42
  ) -> str:
49
43
  try:
44
+ # Use wallet provider signer to satisfy eth_account.BaseAccount interface requirements
50
45
  base_url = (config.open_api_base_url or "").rstrip("/")
51
46
  if not base_url:
52
- raise ValueError("X402 API base URL is not configured.")
53
-
54
- # Use wallet provider signer to satisfy eth_account.BaseAccount interface requirements
55
- context = self.get_context()
56
- wallet_provider = await get_wallet_provider(context.agent)
57
- account = ThreadSafeEvmWalletSigner(wallet_provider)
58
-
59
- payload: Dict[str, Any] = {
47
+ raise ToolException("X402 API base URL is not configured.")
48
+ target_url = f"{base_url}/x402"
49
+ payload: dict[str, Any] = {
60
50
  "agent_id": agent_id,
61
51
  "message": message,
62
52
  "app_id": "skill",
@@ -66,12 +56,8 @@ class X402AskAgent(X402BaseSkill):
66
56
  if super_mode is not None:
67
57
  payload["super_mode"] = super_mode
68
58
 
69
- async with x402HttpxClient(
70
- account=account,
71
- base_url=base_url,
72
- timeout=20.0,
73
- ) as client:
74
- response = await client.post("/x402", json=payload)
59
+ async with self.http_client(timeout=20.0) as client:
60
+ response = await client.post(target_url, json=payload)
75
61
  try:
76
62
  response.raise_for_status()
77
63
  except Exception as e:
@@ -110,55 +96,3 @@ class X402AskAgent(X402BaseSkill):
110
96
  except Exception as e:
111
97
  logger.error(f"Unexpected error in x402_ask_agent: {str(e)}")
112
98
  raise ToolException(f"Unexpected error occurred: {str(e)}") from e
113
-
114
-
115
- class ThreadSafeEvmWalletSigner(CoinbaseEvmWalletSigner):
116
- """EVM wallet signer that avoids nested event loop errors.
117
-
118
- Coinbase's signer runs async wallet calls in the current thread. When invoked
119
- inside an active asyncio loop (as happens in async skills), it trips over the
120
- loop already running. We hop work to a background thread so the provider can
121
- spin up its own loop safely.
122
- """
123
-
124
- def _run_in_thread(self, func: Any, *args: Any, **kwargs: Any) -> Any:
125
- result: list[Any] = []
126
- error: list[BaseException] = []
127
-
128
- def _target() -> None:
129
- try:
130
- result.append(func(*args, **kwargs))
131
- except BaseException as exc: # pragma: no cover - bubble up original error
132
- error.append(exc)
133
-
134
- thread = threading.Thread(target=_target, daemon=True)
135
- thread.start()
136
- thread.join()
137
-
138
- if error:
139
- raise error[0]
140
- return result[0] if result else None
141
-
142
- def unsafe_sign_hash(self, message_hash: Any) -> Any:
143
- return self._run_in_thread(super().unsafe_sign_hash, message_hash)
144
-
145
- def sign_message(self, signable_message: Any) -> Any:
146
- return self._run_in_thread(super().sign_message, signable_message)
147
-
148
- def sign_transaction(self, transaction_dict: Any) -> Any:
149
- return self._run_in_thread(super().sign_transaction, transaction_dict)
150
-
151
- def sign_typed_data(
152
- self,
153
- domain_data: Optional[Dict[str, Any]] = None,
154
- message_types: Optional[Dict[str, Any]] = None,
155
- message_data: Optional[Dict[str, Any]] = None,
156
- full_message: Optional[Dict[str, Any]] = None,
157
- ) -> Any:
158
- return self._run_in_thread(
159
- super().sign_typed_data,
160
- domain_data=domain_data,
161
- message_types=message_types,
162
- message_data=message_data,
163
- full_message=full_message,
164
- )
@@ -1,5 +1,19 @@
1
+ import logging
2
+ import threading
3
+ from collections.abc import AsyncIterator
4
+ from contextlib import asynccontextmanager
5
+ from typing import Any
6
+
7
+ from coinbase_agentkit.wallet_providers.evm_wallet_provider import (
8
+ EvmWalletSigner as CoinbaseEvmWalletSigner,
9
+ )
10
+ from x402.clients.httpx import x402HttpxClient
11
+
12
+ from intentkit.clients import get_wallet_provider
1
13
  from intentkit.skills.onchain import IntentKitOnChainSkill
2
14
 
15
+ logger = logging.getLogger(__name__)
16
+
3
17
 
4
18
  class X402BaseSkill(IntentKitOnChainSkill):
5
19
  """Base class for x402 skills."""
@@ -7,3 +21,79 @@ class X402BaseSkill(IntentKitOnChainSkill):
7
21
  @property
8
22
  def category(self) -> str:
9
23
  return "x402"
24
+
25
+ async def _get_signer(self) -> "ThreadSafeEvmWalletSigner":
26
+ context = self.get_context()
27
+ wallet_provider = await get_wallet_provider(context.agent)
28
+ return ThreadSafeEvmWalletSigner(wallet_provider)
29
+
30
+ @asynccontextmanager
31
+ async def http_client(
32
+ self,
33
+ timeout: float = 30.0,
34
+ ) -> AsyncIterator[x402HttpxClient]:
35
+ account = await self._get_signer()
36
+ try:
37
+ async with x402HttpxClient(
38
+ account=account,
39
+ timeout=timeout,
40
+ ) as client:
41
+ yield client
42
+ except Exception:
43
+ logger.exception("Failed to create x402 HTTP client")
44
+ raise
45
+
46
+
47
+ class ThreadSafeEvmWalletSigner(CoinbaseEvmWalletSigner):
48
+ """EVM wallet signer that avoids nested event loop errors.
49
+
50
+ Coinbase's signer runs async wallet calls in the current thread. When invoked
51
+ inside an active asyncio loop (as happens in async skills), it trips over the
52
+ loop already running. We hop work to a background thread so the provider can
53
+ spin up its own loop safely.
54
+ """
55
+
56
+ def __init__(self, wallet_provider: Any):
57
+ super().__init__(wallet_provider=wallet_provider)
58
+
59
+ def _run_in_thread(self, func: Any, *args: Any, **kwargs: Any) -> Any:
60
+ result: list[Any] = []
61
+ error: list[BaseException] = []
62
+
63
+ def _target() -> None:
64
+ try:
65
+ result.append(func(*args, **kwargs))
66
+ except BaseException as exc: # pragma: no cover - bubble up original error
67
+ error.append(exc)
68
+
69
+ thread = threading.Thread(target=_target, daemon=True)
70
+ thread.start()
71
+ thread.join()
72
+
73
+ if error:
74
+ raise error[0]
75
+ return result[0] if result else None
76
+
77
+ def unsafe_sign_hash(self, message_hash: Any) -> Any:
78
+ return self._run_in_thread(super().unsafe_sign_hash, message_hash)
79
+
80
+ def sign_message(self, signable_message: Any) -> Any:
81
+ return self._run_in_thread(super().sign_message, signable_message)
82
+
83
+ def sign_transaction(self, transaction_dict: Any) -> Any:
84
+ return self._run_in_thread(super().sign_transaction, transaction_dict)
85
+
86
+ def sign_typed_data(
87
+ self,
88
+ domain_data: Any | None = None,
89
+ message_types: Any | None = None,
90
+ message_data: Any | None = None,
91
+ full_message: Any | None = None,
92
+ ) -> Any:
93
+ return self._run_in_thread(
94
+ super().sign_typed_data,
95
+ domain_data=domain_data,
96
+ message_types=message_types,
97
+ message_data=message_data,
98
+ full_message=full_message,
99
+ )
@@ -0,0 +1,117 @@
1
+ import logging
2
+ from typing import Any
3
+ from urllib.parse import urlparse
4
+
5
+ import httpx
6
+ from langchain_core.tools import ToolException
7
+ from pydantic import BaseModel, Field
8
+
9
+ from intentkit.skills.x402.base import X402BaseSkill
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class X402HttpRequestInput(BaseModel):
15
+ """Arguments for a generic x402 HTTP request."""
16
+
17
+ method: str = Field(description="HTTP method to use. Supported values: GET, POST.")
18
+ url: str = Field(
19
+ description="Absolute URL for the request (must include scheme and host)."
20
+ )
21
+ headers: dict[str, str] | None = Field(
22
+ default=None,
23
+ description="Optional headers to include in the request.",
24
+ )
25
+ params: dict[str, Any] | None = Field(
26
+ default=None,
27
+ description="Optional query parameters to include in the request.",
28
+ )
29
+ data: dict[str, Any] | str | None = Field(
30
+ default=None,
31
+ description=(
32
+ "Optional request body. Dictionaries are sent as JSON; strings are sent as raw data. "
33
+ "Only supported for POST requests."
34
+ ),
35
+ )
36
+ timeout: float | None = Field(
37
+ default=30.0,
38
+ description="Request timeout in seconds.",
39
+ )
40
+
41
+
42
+ class X402HttpRequest(X402BaseSkill):
43
+ """Skill that performs signed HTTP requests via the x402 client."""
44
+
45
+ name: str = "x402_http_request"
46
+ description: str = (
47
+ "Send an HTTP GET or POST request using the x402 payment protocol. "
48
+ "Provide the method, absolute URL, optional headers, query parameters, and request body. "
49
+ "Returns the response status and body text."
50
+ )
51
+ args_schema: type[BaseModel] = X402HttpRequestInput
52
+
53
+ async def _arun(
54
+ self,
55
+ method: str,
56
+ url: str,
57
+ headers: dict[str, str] | None = None,
58
+ params: dict[str, Any] | None = None,
59
+ data: dict[str, Any] | str | None = None,
60
+ timeout: float = 30.0,
61
+ **_: Any,
62
+ ) -> str:
63
+ method_upper = method.upper()
64
+ if method_upper not in {"GET", "POST"}:
65
+ raise ToolException(
66
+ f"Unsupported HTTP method '{method}'. Only GET and POST are allowed."
67
+ )
68
+
69
+ parsed = urlparse(url)
70
+ if not (parsed.scheme and parsed.netloc):
71
+ raise ToolException("URL must include scheme and host (absolute URL).")
72
+
73
+ request_headers = dict(headers or {})
74
+ request_kwargs: dict[str, Any] = {
75
+ "url": url,
76
+ "headers": request_headers or None,
77
+ "params": params,
78
+ "timeout": timeout,
79
+ }
80
+
81
+ if method_upper == "POST":
82
+ if isinstance(data, dict):
83
+ header_keys = {key.lower() for key in request_headers}
84
+ if "content-type" not in header_keys:
85
+ request_headers["Content-Type"] = "application/json"
86
+ request_kwargs["json"] = data
87
+ elif isinstance(data, str):
88
+ request_kwargs["content"] = data
89
+ elif data is not None:
90
+ raise ToolException(
91
+ "POST body must be either a JSON-serializable object or a string."
92
+ )
93
+ elif data is not None:
94
+ raise ToolException("Request body is only supported for POST requests.")
95
+
96
+ try:
97
+ async with self.http_client(timeout=timeout) as client:
98
+ response = await client.request(method_upper, **request_kwargs)
99
+ response.raise_for_status()
100
+ return f"Status: {response.status_code}\nContent: {response.text}"
101
+ except ValueError as exc:
102
+ raise ToolException(str(exc)) from exc
103
+ except httpx.TimeoutException as exc:
104
+ raise ToolException(
105
+ f"Request to {url} timed out after {timeout} seconds"
106
+ ) from exc
107
+ except httpx.HTTPStatusError as exc:
108
+ raise ToolException(
109
+ f"HTTP {exc.response.status_code} - {exc.response.text}"
110
+ ) from exc
111
+ except httpx.RequestError as exc:
112
+ raise ToolException(f"Failed to connect to {url} - {str(exc)}") from exc
113
+ except ToolException:
114
+ raise
115
+ except Exception as exc:
116
+ logger.error("Unexpected error in x402_http_request", exc_info=exc)
117
+ raise ToolException(f"Unexpected error occurred - {str(exc)}") from exc
@@ -4,10 +4,7 @@
4
4
  "title": "x402",
5
5
  "description": "Interact with other IntentKit agents through the x402 payment protocol.",
6
6
  "x-icon": "https://ai.service.crestal.dev/skills/x402/x402.webp",
7
- "x-tags": [
8
- "Communication",
9
- "Infrastructure"
10
- ],
7
+ "x-tags": ["Communication", "Infrastructure"],
11
8
  "properties": {
12
9
  "enabled": {
13
10
  "type": "boolean",
@@ -21,11 +18,7 @@
21
18
  "x402_ask_agent": {
22
19
  "type": "string",
23
20
  "title": "Ask Agent",
24
- "enum": [
25
- "disabled",
26
- "public",
27
- "private"
28
- ],
21
+ "enum": ["disabled", "public", "private"],
29
22
  "x-enum-title": [
30
23
  "Disabled",
31
24
  "Agent Owner + All Users",
@@ -33,8 +26,20 @@
33
26
  ],
34
27
  "description": "Send a message to another IntentKit agent via the x402 API and return the final agent response.",
35
28
  "default": "disabled"
29
+ },
30
+ "x402_http_request": {
31
+ "type": "string",
32
+ "title": "HTTP Request",
33
+ "enum": ["disabled", "public", "private"],
34
+ "x-enum-title": [
35
+ "Disabled",
36
+ "Agent Owner + All Users",
37
+ "Agent Owner Only"
38
+ ],
39
+ "description": "Perform a signed HTTP GET or POST request via the x402 payment protocol.",
40
+ "default": "disabled"
36
41
  }
37
42
  }
38
43
  }
39
44
  }
40
- }
45
+ }
@@ -1,4 +1,4 @@
1
- from typing import Dict, Literal
1
+ from typing import Literal
2
2
 
3
3
  from intentkit.skills.onchain import IntentKitOnChainSkill
4
4
 
@@ -10,7 +10,7 @@ class XmtpBaseTool(IntentKitOnChainSkill):
10
10
  response_format: Literal["content", "content_and_artifact"] = "content_and_artifact"
11
11
 
12
12
  # ChainId mapping for XMTP wallet_sendCalls (mainnet only)
13
- CHAIN_ID_HEX_BY_NETWORK: Dict[str, str] = {
13
+ CHAIN_ID_HEX_BY_NETWORK: dict[str, str] = {
14
14
  "ethereum-mainnet": "0x1", # 1
15
15
  "base-mainnet": "0x2105", # 8453
16
16
  "arbitrum-mainnet": "0xA4B1", # 42161
@@ -18,7 +18,7 @@ class XmtpBaseTool(IntentKitOnChainSkill):
18
18
  }
19
19
 
20
20
  # CDP network mapping for swap quote API (mainnet only)
21
- NETWORK_FOR_CDP_MAPPING: Dict[str, str] = {
21
+ NETWORK_FOR_CDP_MAPPING: dict[str, str] = {
22
22
  "ethereum-mainnet": "ethereum",
23
23
  "base-mainnet": "base",
24
24
  "arbitrum-mainnet": "arbitrum",
@@ -1,4 +1,4 @@
1
- from typing import Literal, Type
1
+ from typing import Literal
2
2
 
3
3
  from langchain_core.tools.base import ToolException
4
4
  from pydantic import BaseModel, Field
@@ -24,7 +24,7 @@ class XmtpGetSwapPrice(XmtpBaseTool):
24
24
  name: str = "xmtp_get_swap_price"
25
25
  description: str = "Get an indicative swap price/quote for token pair and amount on Ethereum, Base, Arbitrum, and Optimism mainnet networks using CDP."
26
26
  response_format: Literal["content", "content_and_artifact"] = "content"
27
- args_schema: Type[BaseModel] = SwapPriceInput
27
+ args_schema: type[BaseModel] = SwapPriceInput
28
28
 
29
29
  async def _arun(
30
30
  self,
@@ -1,5 +1,3 @@
1
- from typing import List, Tuple, Type
2
-
3
1
  from pydantic import BaseModel, Field
4
2
 
5
3
  from intentkit.clients.cdp import get_cdp_client
@@ -42,7 +40,7 @@ class XmtpSwap(XmtpBaseTool):
42
40
  "Returns a wallet_sendCalls payload that can include an optional approval call and the swap call. "
43
41
  "Supports Ethereum, Base, Arbitrum, and Optimism mainnet networks."
44
42
  )
45
- args_schema: Type[BaseModel] = SwapInput
43
+ args_schema: type[BaseModel] = SwapInput
46
44
 
47
45
  async def _arun(
48
46
  self,
@@ -51,7 +49,7 @@ class XmtpSwap(XmtpBaseTool):
51
49
  to_token: str,
52
50
  from_amount: str,
53
51
  slippage_bps: int = 100,
54
- ) -> Tuple[str, List[ChatMessageAttachment]]:
52
+ ) -> tuple[str, list[ChatMessageAttachment]]:
55
53
  # Input validation
56
54
  if (
57
55
  not from_address