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
@@ -0,0 +1,98 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ from langchain_core.tools import ToolException
5
+ from pydantic import BaseModel, Field
6
+
7
+ from intentkit.config.config import config
8
+ from intentkit.models.chat import AuthorType
9
+ from intentkit.skills.x402.base import X402BaseSkill
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class AskAgentInput(BaseModel):
15
+ """Arguments for the x402 ask agent skill."""
16
+
17
+ agent_id: str = Field(description="ID or slug of the agent to query.")
18
+ message: str = Field(description="Message to send to the target agent.")
19
+ search_mode: bool | None = Field(
20
+ default=None, description="Enable search mode when interacting with the agent."
21
+ )
22
+ super_mode: bool | None = Field(
23
+ default=None, description="Enable super mode when interacting with the agent."
24
+ )
25
+
26
+
27
+ class X402AskAgent(X402BaseSkill):
28
+ """Skill that queries another agent via the x402 API."""
29
+
30
+ name: str = "x402_ask_agent"
31
+ description: str = (
32
+ "Call another agent through the x402 API and return the final agent message."
33
+ )
34
+ args_schema: type[BaseModel] = AskAgentInput
35
+
36
+ async def _arun(
37
+ self,
38
+ agent_id: str,
39
+ message: str,
40
+ search_mode: bool | None = None,
41
+ super_mode: bool | None = None,
42
+ ) -> str:
43
+ try:
44
+ # Use wallet provider signer to satisfy eth_account.BaseAccount interface requirements
45
+ base_url = (config.open_api_base_url or "").rstrip("/")
46
+ if not base_url:
47
+ raise ToolException("X402 API base URL is not configured.")
48
+ target_url = f"{base_url}/x402"
49
+ payload: dict[str, Any] = {
50
+ "agent_id": agent_id,
51
+ "message": message,
52
+ "app_id": "skill",
53
+ }
54
+ if search_mode is not None:
55
+ payload["search_mode"] = search_mode
56
+ if super_mode is not None:
57
+ payload["super_mode"] = super_mode
58
+
59
+ async with self.http_client(timeout=20.0) as client:
60
+ response = await client.post(target_url, json=payload)
61
+ try:
62
+ response.raise_for_status()
63
+ except Exception as e:
64
+ error_body = ""
65
+ try:
66
+ error_body = response.text
67
+ except Exception:
68
+ error_body = "Unable to read response body"
69
+ logger.error(
70
+ f"HTTP request failed with status {response.status_code}: {error_body}"
71
+ )
72
+ raise ToolException(
73
+ f"HTTP request failed with status {response.status_code}: {error_body}"
74
+ ) from e
75
+ messages = response.json()
76
+ if not isinstance(messages, list) or not messages:
77
+ raise ValueError("Agent returned an empty response.")
78
+
79
+ last_message = messages[-1]
80
+ if not isinstance(last_message, dict):
81
+ raise ValueError("Agent response format is invalid.")
82
+
83
+ author_type = last_message.get("author_type")
84
+ content = last_message.get("message")
85
+
86
+ if author_type == AuthorType.SYSTEM.value:
87
+ raise ToolException(content or "Agent returned a system message.")
88
+
89
+ if not content:
90
+ raise ToolException("Agent response did not include message text.")
91
+
92
+ return str(content)
93
+ except ToolException:
94
+ # Re-raise ToolException as-is
95
+ raise
96
+ except Exception as e:
97
+ logger.error(f"Unexpected error in x402_ask_agent: {str(e)}")
98
+ raise ToolException(f"Unexpected error occurred: {str(e)}") from e
@@ -0,0 +1,99 @@
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
13
+ from intentkit.skills.onchain import IntentKitOnChainSkill
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class X402BaseSkill(IntentKitOnChainSkill):
19
+ """Base class for x402 skills."""
20
+
21
+ @property
22
+ def category(self) -> str:
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
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "title": "x402",
5
+ "description": "Interact with other IntentKit agents through the x402 payment protocol.",
6
+ "x-icon": "https://ai.service.crestal.dev/skills/x402/x402.webp",
7
+ "x-tags": ["Communication", "Infrastructure"],
8
+ "properties": {
9
+ "enabled": {
10
+ "type": "boolean",
11
+ "title": "Enabled",
12
+ "description": "Whether this skill category is enabled.",
13
+ "default": false
14
+ },
15
+ "states": {
16
+ "type": "object",
17
+ "properties": {
18
+ "x402_ask_agent": {
19
+ "type": "string",
20
+ "title": "Ask Agent",
21
+ "enum": ["disabled", "public", "private"],
22
+ "x-enum-title": [
23
+ "Disabled",
24
+ "Agent Owner + All Users",
25
+ "Agent Owner Only"
26
+ ],
27
+ "description": "Send a message to another IntentKit agent via the x402 API and return the final agent response.",
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"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
Binary file
@@ -3,7 +3,6 @@
3
3
  import logging
4
4
  from typing import TypedDict
5
5
 
6
- from intentkit.abstracts.skill import SkillStoreABC
7
6
  from intentkit.skills.base import SkillConfig, SkillState
8
7
  from intentkit.skills.xmtp.base import XmtpBaseTool
9
8
  from intentkit.skills.xmtp.price import XmtpGetSwapPrice
@@ -31,7 +30,6 @@ class Config(SkillConfig):
31
30
  async def get_skills(
32
31
  config: "Config",
33
32
  is_private: bool,
34
- store: SkillStoreABC,
35
33
  **_,
36
34
  ) -> list[XmtpBaseTool]:
37
35
  """Get all XMTP skills.
@@ -39,7 +37,6 @@ async def get_skills(
39
37
  Args:
40
38
  config: The configuration for XMTP skills.
41
39
  is_private: Whether to include private skills.
42
- store: The skill store for persisting data.
43
40
 
44
41
  Returns:
45
42
  A list of XMTP skills.
@@ -56,7 +53,7 @@ async def get_skills(
56
53
  # Get each skill using the cached getter
57
54
  result = []
58
55
  for name in available_skills:
59
- skill = get_xmtp_skill(name, store)
56
+ skill = get_xmtp_skill(name)
60
57
  if skill:
61
58
  result.append(skill)
62
59
  return result
@@ -64,34 +61,26 @@ async def get_skills(
64
61
 
65
62
  def get_xmtp_skill(
66
63
  name: str,
67
- store: SkillStoreABC,
68
64
  ) -> XmtpBaseTool:
69
65
  """Get an XMTP skill by name.
70
66
 
71
67
  Args:
72
68
  name: The name of the skill to get
73
- store: The skill store for persisting data
74
69
 
75
70
  Returns:
76
71
  The requested XMTP skill
77
72
  """
78
73
  if name == "xmtp_transfer":
79
74
  if name not in _cache:
80
- _cache[name] = XmtpTransfer(
81
- skill_store=store,
82
- )
75
+ _cache[name] = XmtpTransfer()
83
76
  return _cache[name]
84
77
  elif name == "xmtp_swap":
85
78
  if name not in _cache:
86
- _cache[name] = XmtpSwap(
87
- skill_store=store,
88
- )
79
+ _cache[name] = XmtpSwap()
89
80
  return _cache[name]
90
81
  elif name == "xmtp_get_swap_price":
91
82
  if name not in _cache:
92
- _cache[name] = XmtpGetSwapPrice(
93
- skill_store=store,
94
- )
83
+ _cache[name] = XmtpGetSwapPrice()
95
84
  return _cache[name]
96
85
  else:
97
86
  logger.warning(f"Unknown XMTP skill: {name}")
@@ -1,16 +1,16 @@
1
- from typing import Dict, Literal
1
+ from typing import Literal
2
2
 
3
- from intentkit.skills.base import IntentKitSkill
3
+ from intentkit.skills.onchain import IntentKitOnChainSkill
4
4
 
5
5
 
6
- class XmtpBaseTool(IntentKitSkill):
6
+ class XmtpBaseTool(IntentKitOnChainSkill):
7
7
  """Base class for XMTP-related skills."""
8
8
 
9
9
  # Set response format to content_and_artifact for returning tuple
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(IntentKitSkill):
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,9 +1,9 @@
1
- from typing import Literal, Type
1
+ from typing import Literal
2
2
 
3
- from langchain.tools.base import ToolException
3
+ from langchain_core.tools.base import ToolException
4
4
  from pydantic import BaseModel, Field
5
5
 
6
- from intentkit.clients.cdp import get_origin_cdp_client
6
+ from intentkit.clients.cdp import get_cdp_client
7
7
  from intentkit.skills.xmtp.base import XmtpBaseTool
8
8
 
9
9
 
@@ -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,
@@ -50,8 +50,8 @@ class XmtpGetSwapPrice(XmtpBaseTool):
50
50
 
51
51
  network_for_cdp = self.get_cdp_network(agent.network_id)
52
52
 
53
- cdp_client = get_origin_cdp_client()
54
- # Note: Don't use async with context manager as get_origin_cdp_client returns a managed global client
53
+ cdp_client = get_cdp_client()
54
+ # Note: Don't use async with context manager as get_cdp_client returns a managed global client
55
55
  price = await cdp_client.evm.get_swap_price(
56
56
  from_token=from_token,
57
57
  to_token=to_token,
@@ -1,8 +1,6 @@
1
- from typing import List, Tuple, Type
2
-
3
1
  from pydantic import BaseModel, Field
4
2
 
5
- from intentkit.clients.cdp import get_origin_cdp_client
3
+ from intentkit.clients.cdp import get_cdp_client
6
4
  from intentkit.models.chat import ChatMessageAttachment, ChatMessageAttachmentType
7
5
  from intentkit.skills.xmtp.base import XmtpBaseTool
8
6
 
@@ -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
@@ -107,15 +105,15 @@ class XmtpSwap(XmtpBaseTool):
107
105
  # https://github.com/coinbase/cdp-sdk/blob/main/examples/python/evm/swaps/create_swap_quote.py
108
106
  network_for_cdp = self.get_cdp_network(agent.network_id)
109
107
 
110
- # Get CDP client from global origin helper (server-side credentials)
111
- cdp_client = get_origin_cdp_client()
108
+ # Get CDP client from the global helper (server-side credentials)
109
+ cdp_client = get_cdp_client()
112
110
 
113
111
  # Call CDP to create swap quote and extract call datas
114
112
  # Be permissive with response shape across SDK versions
115
113
  try:
116
114
  # Attempt the canonical method per CDP SDK examples
117
115
  # create_swap_quote(from_token, to_token, from_amount, network, taker, slippage_bps, signer_address)
118
- # Note: Don't use async with context manager as get_origin_cdp_client returns a managed global client
116
+ # Note: Don't use async with context manager as get_cdp_client returns a managed global client
119
117
  quote = await cdp_client.evm.create_swap_quote(
120
118
  from_token=from_token,
121
119
  to_token=to_token,
@@ -1,5 +1,3 @@
1
- from typing import List, Optional, Tuple, Type
2
-
3
1
  from pydantic import BaseModel, Field
4
2
  from web3.exceptions import ContractLogicError
5
3
 
@@ -16,7 +14,7 @@ class TransferInput(BaseModel):
16
14
  description="The amount to transfer in human-readable format (e.g., '1.5' for 1.5 ETH, '100' for 100 USDC). Do NOT multiply by token decimals."
17
15
  )
18
16
  currency: str = Field(description="Currency symbol (e.g., 'ETH', 'USDC', 'NATION')")
19
- token_contract_address: Optional[str] = Field(
17
+ token_contract_address: str | None = Field(
20
18
  default=None,
21
19
  description="Token contract address for ERC20 transfers. Leave empty for ETH transfers.",
22
20
  )
@@ -31,7 +29,7 @@ class XmtpTransfer(XmtpBaseTool):
31
29
  that can be sent to users for signing.
32
30
  Supports Ethereum, Polygon, Base, Arbitrum, and Optimism networks (both mainnet and testnet).
33
31
  """
34
- args_schema: Type[BaseModel] = TransferInput
32
+ args_schema: type[BaseModel] = TransferInput
35
33
 
36
34
  async def _arun(
37
35
  self,
@@ -39,8 +37,8 @@ class XmtpTransfer(XmtpBaseTool):
39
37
  to_address: str,
40
38
  amount: str,
41
39
  currency: str,
42
- token_contract_address: Optional[str],
43
- ) -> Tuple[str, List[ChatMessageAttachment]]:
40
+ token_contract_address: str | None,
41
+ ) -> tuple[str, list[ChatMessageAttachment]]:
44
42
  """Create an XMTP transfer transaction request.
45
43
 
46
44
  Args:
intentkit/utils/error.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Optional, Sequence
2
+ from collections.abc import Sequence
3
3
 
4
4
  from fastapi.exceptions import RequestValidationError
5
5
  from fastapi.utils import is_body_allowed_for_status_code
@@ -17,7 +17,7 @@ logger = logging.getLogger(__name__)
17
17
  class RateLimitExceeded(Exception):
18
18
  """Rate limit exceeded"""
19
19
 
20
- def __init__(self, message: Optional[str] = "Rate limit exceeded"):
20
+ def __init__(self, message: str | None = "Rate limit exceeded"):
21
21
  self.message = message
22
22
  super().__init__(self.message)
23
23
 
@@ -4,13 +4,11 @@ Logging configuration module
4
4
 
5
5
  import json
6
6
  import logging
7
- from typing import Callable, Optional
7
+ from collections.abc import Callable
8
8
 
9
9
 
10
10
  class JsonFormatter(logging.Formatter):
11
- def __init__(
12
- self, filter_func: Optional[Callable[[logging.LogRecord], bool]] = None
13
- ):
11
+ def __init__(self, filter_func: Callable[[logging.LogRecord], bool] | None = None):
14
12
  super().__init__()
15
13
  self.filter_func = filter_func
16
14
 
intentkit/utils/s3.py CHANGED
@@ -5,7 +5,6 @@ S3 utility module for storing and retrieving images from AWS S3.
5
5
  import logging
6
6
  from enum import Enum
7
7
  from io import BytesIO
8
- from typing import Optional
9
8
 
10
9
  import boto3
11
10
  import filetype
@@ -16,10 +15,10 @@ from mypy_boto3_s3.client import S3Client
16
15
  logger = logging.getLogger(__name__)
17
16
 
18
17
  # Global variables for S3 configuration
19
- _bucket: Optional[str] = None
20
- _client: Optional[S3Client] = None
21
- _prefix: Optional[str] = None
22
- _cdn_url: Optional[str] = None
18
+ _bucket: str | None = None
19
+ _client: S3Client | None = None
20
+ _prefix: str | None = None
21
+ _cdn_url: str | None = None
23
22
 
24
23
 
25
24
  def init_s3(bucket: str, cdn_url: str, env: str) -> None:
@@ -114,7 +113,7 @@ async def store_image(url: str, key: str) -> str:
114
113
 
115
114
 
116
115
  async def store_image_bytes(
117
- image_bytes: bytes, key: str, content_type: Optional[str] = None
116
+ image_bytes: bytes, key: str, content_type: str | None = None
118
117
  ) -> str:
119
118
  """
120
119
  Store raw image bytes to S3.
@@ -185,8 +184,8 @@ class FileType(str, Enum):
185
184
  async def store_file(
186
185
  content: bytes,
187
186
  key: str,
188
- content_type: Optional[str] = None,
189
- size: Optional[int] = None,
187
+ content_type: str | None = None,
188
+ size: int | None = None,
190
189
  ) -> str:
191
190
  """Store raw file bytes with automatic content type detection."""
192
191
  if not _client or not _bucket or not _prefix or not _cdn_url:
@@ -239,7 +238,7 @@ async def store_file_bytes(
239
238
  file_bytes: bytes,
240
239
  key: str,
241
240
  file_type: FileType,
242
- size_limit_bytes: Optional[int] = None,
241
+ size_limit_bytes: int | None = None,
243
242
  ) -> str:
244
243
  """
245
244
  Store raw file bytes (image, video, sound, pdf) to S3.