intentkit 0.6.13.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 (385) 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 +14 -7
  5. intentkit/abstracts/skill.py +6 -144
  6. intentkit/abstracts/twitter.py +4 -5
  7. intentkit/clients/__init__.py +5 -2
  8. intentkit/clients/cdp.py +101 -141
  9. intentkit/clients/twitter.py +83 -62
  10. intentkit/clients/web3.py +29 -0
  11. intentkit/config/config.py +8 -5
  12. intentkit/core/agent.py +472 -195
  13. intentkit/core/asset.py +253 -0
  14. intentkit/core/chat.py +51 -0
  15. intentkit/core/client.py +1 -1
  16. intentkit/core/credit.py +460 -130
  17. intentkit/core/engine.py +262 -233
  18. intentkit/core/node.py +15 -16
  19. intentkit/core/prompt.py +62 -28
  20. intentkit/core/scheduler.py +92 -0
  21. intentkit/core/statistics.py +168 -0
  22. intentkit/models/agent.py +1096 -949
  23. intentkit/models/agent_data.py +68 -38
  24. intentkit/models/agent_public.json +98 -0
  25. intentkit/models/agent_schema.json +54 -439
  26. intentkit/models/app_setting.py +96 -33
  27. intentkit/models/chat.py +74 -27
  28. intentkit/models/conversation.py +8 -8
  29. intentkit/models/credit.py +362 -74
  30. intentkit/models/db.py +26 -8
  31. intentkit/models/db_mig.py +2 -2
  32. intentkit/models/llm.csv +28 -0
  33. intentkit/models/llm.py +185 -350
  34. intentkit/models/redis.py +6 -4
  35. intentkit/models/skill.py +186 -72
  36. intentkit/models/skills.csv +174 -0
  37. intentkit/models/user.py +82 -24
  38. intentkit/skills/acolyt/__init__.py +2 -9
  39. intentkit/skills/acolyt/ask.py +3 -4
  40. intentkit/skills/acolyt/base.py +4 -9
  41. intentkit/skills/acolyt/schema.json +4 -3
  42. intentkit/skills/aixbt/__init__.py +2 -13
  43. intentkit/skills/aixbt/base.py +1 -7
  44. intentkit/skills/aixbt/projects.py +14 -15
  45. intentkit/skills/aixbt/schema.json +4 -4
  46. intentkit/skills/allora/__init__.py +2 -9
  47. intentkit/skills/allora/base.py +4 -9
  48. intentkit/skills/allora/price.py +3 -4
  49. intentkit/skills/allora/schema.json +3 -2
  50. intentkit/skills/base.py +248 -85
  51. intentkit/skills/basename/__init__.py +51 -0
  52. intentkit/skills/basename/base.py +11 -0
  53. intentkit/skills/basename/basename.svg +11 -0
  54. intentkit/skills/basename/schema.json +58 -0
  55. intentkit/skills/carv/__init__.py +115 -121
  56. intentkit/skills/carv/base.py +184 -185
  57. intentkit/skills/carv/fetch_news.py +3 -3
  58. intentkit/skills/carv/onchain_query.py +4 -4
  59. intentkit/skills/carv/schema.json +134 -137
  60. intentkit/skills/carv/token_info_and_price.py +5 -5
  61. intentkit/skills/casino/README.md +254 -0
  62. intentkit/skills/casino/__init__.py +86 -0
  63. intentkit/skills/casino/base.py +17 -0
  64. intentkit/skills/casino/casino.png +0 -0
  65. intentkit/skills/casino/deck_draw.py +127 -0
  66. intentkit/skills/casino/deck_shuffle.py +118 -0
  67. intentkit/skills/casino/dice_roll.py +100 -0
  68. intentkit/skills/casino/schema.json +77 -0
  69. intentkit/skills/casino/utils.py +107 -0
  70. intentkit/skills/cdp/__init__.py +22 -84
  71. intentkit/skills/cdp/base.py +1 -7
  72. intentkit/skills/cdp/schema.json +11 -314
  73. intentkit/skills/chainlist/__init__.py +2 -7
  74. intentkit/skills/chainlist/base.py +1 -7
  75. intentkit/skills/chainlist/chain_lookup.py +18 -18
  76. intentkit/skills/chainlist/schema.json +3 -5
  77. intentkit/skills/common/__init__.py +2 -9
  78. intentkit/skills/common/base.py +1 -7
  79. intentkit/skills/common/current_time.py +1 -2
  80. intentkit/skills/common/schema.json +2 -2
  81. intentkit/skills/cookiefun/__init__.py +6 -9
  82. intentkit/skills/cookiefun/base.py +2 -7
  83. intentkit/skills/cookiefun/get_account_details.py +7 -7
  84. intentkit/skills/cookiefun/get_account_feed.py +19 -19
  85. intentkit/skills/cookiefun/get_account_smart_followers.py +7 -7
  86. intentkit/skills/cookiefun/get_sectors.py +3 -3
  87. intentkit/skills/cookiefun/schema.json +1 -3
  88. intentkit/skills/cookiefun/search_accounts.py +9 -9
  89. intentkit/skills/cryptocompare/__init__.py +7 -24
  90. intentkit/skills/cryptocompare/api.py +2 -3
  91. intentkit/skills/cryptocompare/base.py +11 -25
  92. intentkit/skills/cryptocompare/fetch_news.py +4 -5
  93. intentkit/skills/cryptocompare/fetch_price.py +6 -7
  94. intentkit/skills/cryptocompare/fetch_top_exchanges.py +4 -5
  95. intentkit/skills/cryptocompare/fetch_top_market_cap.py +4 -5
  96. intentkit/skills/cryptocompare/fetch_top_volume.py +4 -5
  97. intentkit/skills/cryptocompare/fetch_trading_signals.py +5 -6
  98. intentkit/skills/cryptocompare/schema.json +3 -3
  99. intentkit/skills/cryptopanic/__init__.py +7 -10
  100. intentkit/skills/cryptopanic/base.py +51 -55
  101. intentkit/skills/cryptopanic/fetch_crypto_news.py +4 -8
  102. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +5 -7
  103. intentkit/skills/cryptopanic/schema.json +105 -103
  104. intentkit/skills/dapplooker/__init__.py +2 -9
  105. intentkit/skills/dapplooker/base.py +4 -9
  106. intentkit/skills/dapplooker/dapplooker_token_data.py +7 -7
  107. intentkit/skills/dapplooker/schema.json +3 -5
  108. intentkit/skills/defillama/__init__.py +24 -74
  109. intentkit/skills/defillama/api.py +6 -9
  110. intentkit/skills/defillama/base.py +11 -21
  111. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +8 -10
  112. intentkit/skills/defillama/coins/fetch_block.py +6 -8
  113. intentkit/skills/defillama/coins/fetch_current_prices.py +8 -10
  114. intentkit/skills/defillama/coins/fetch_first_price.py +7 -9
  115. intentkit/skills/defillama/coins/fetch_historical_prices.py +9 -11
  116. intentkit/skills/defillama/coins/fetch_price_chart.py +9 -11
  117. intentkit/skills/defillama/coins/fetch_price_percentage.py +7 -9
  118. intentkit/skills/defillama/config/chains.py +1 -3
  119. intentkit/skills/defillama/fees/fetch_fees_overview.py +24 -26
  120. intentkit/skills/defillama/schema.json +5 -1
  121. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +16 -18
  122. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +8 -10
  123. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +5 -7
  124. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +7 -9
  125. intentkit/skills/defillama/tests/api_integration.test.py +1 -1
  126. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +4 -6
  127. intentkit/skills/defillama/tvl/fetch_chains.py +9 -11
  128. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +4 -6
  129. intentkit/skills/defillama/tvl/fetch_protocol.py +32 -38
  130. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +3 -5
  131. intentkit/skills/defillama/tvl/fetch_protocols.py +37 -45
  132. intentkit/skills/defillama/volumes/fetch_dex_overview.py +42 -48
  133. intentkit/skills/defillama/volumes/fetch_dex_summary.py +35 -37
  134. intentkit/skills/defillama/volumes/fetch_options_overview.py +24 -28
  135. intentkit/skills/defillama/yields/fetch_pool_chart.py +10 -12
  136. intentkit/skills/defillama/yields/fetch_pools.py +26 -30
  137. intentkit/skills/dexscreener/README.md +154 -0
  138. intentkit/skills/dexscreener/__init__.py +97 -93
  139. intentkit/skills/dexscreener/base.py +125 -133
  140. intentkit/skills/dexscreener/get_pair_info.py +158 -0
  141. intentkit/skills/dexscreener/get_token_pairs.py +165 -0
  142. intentkit/skills/dexscreener/get_tokens_info.py +212 -0
  143. intentkit/skills/dexscreener/model/search_token_response.py +80 -82
  144. intentkit/skills/dexscreener/schema.json +91 -48
  145. intentkit/skills/dexscreener/search_token.py +182 -321
  146. intentkit/skills/dexscreener/utils.py +420 -0
  147. intentkit/skills/dune_analytics/__init__.py +7 -9
  148. intentkit/skills/dune_analytics/base.py +48 -52
  149. intentkit/skills/dune_analytics/fetch_kol_buys.py +5 -7
  150. intentkit/skills/dune_analytics/fetch_nation_metrics.py +6 -8
  151. intentkit/skills/dune_analytics/schema.json +104 -99
  152. intentkit/skills/elfa/__init__.py +5 -18
  153. intentkit/skills/elfa/base.py +10 -14
  154. intentkit/skills/elfa/mention.py +19 -21
  155. intentkit/skills/elfa/schema.json +3 -2
  156. intentkit/skills/elfa/stats.py +4 -4
  157. intentkit/skills/elfa/tokens.py +12 -12
  158. intentkit/skills/elfa/utils.py +26 -28
  159. intentkit/skills/enso/__init__.py +11 -31
  160. intentkit/skills/enso/base.py +50 -35
  161. intentkit/skills/enso/best_yield.py +16 -24
  162. intentkit/skills/enso/networks.py +6 -11
  163. intentkit/skills/enso/prices.py +11 -13
  164. intentkit/skills/enso/route.py +34 -38
  165. intentkit/skills/enso/schema.json +3 -2
  166. intentkit/skills/enso/tokens.py +29 -38
  167. intentkit/skills/enso/wallet.py +76 -191
  168. intentkit/skills/erc20/__init__.py +50 -0
  169. intentkit/skills/erc20/base.py +11 -0
  170. intentkit/skills/erc20/erc20.svg +5 -0
  171. intentkit/skills/erc20/schema.json +74 -0
  172. intentkit/skills/erc721/__init__.py +53 -0
  173. intentkit/skills/erc721/base.py +11 -0
  174. intentkit/skills/erc721/erc721.svg +5 -0
  175. intentkit/skills/erc721/schema.json +90 -0
  176. intentkit/skills/firecrawl/README.md +11 -5
  177. intentkit/skills/firecrawl/__init__.py +5 -18
  178. intentkit/skills/firecrawl/base.py +4 -11
  179. intentkit/skills/firecrawl/clear.py +4 -8
  180. intentkit/skills/firecrawl/crawl.py +19 -19
  181. intentkit/skills/firecrawl/query.py +4 -3
  182. intentkit/skills/firecrawl/schema.json +6 -8
  183. intentkit/skills/firecrawl/scrape.py +150 -40
  184. intentkit/skills/firecrawl/utils.py +50 -42
  185. intentkit/skills/github/__init__.py +2 -7
  186. intentkit/skills/github/base.py +1 -7
  187. intentkit/skills/github/github_search.py +1 -2
  188. intentkit/skills/github/schema.json +3 -4
  189. intentkit/skills/heurist/__init__.py +8 -27
  190. intentkit/skills/heurist/base.py +4 -9
  191. intentkit/skills/heurist/image_generation_animagine_xl.py +12 -13
  192. intentkit/skills/heurist/image_generation_arthemy_comics.py +12 -13
  193. intentkit/skills/heurist/image_generation_arthemy_real.py +12 -13
  194. intentkit/skills/heurist/image_generation_braindance.py +12 -13
  195. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +12 -13
  196. intentkit/skills/heurist/image_generation_flux_1_dev.py +12 -13
  197. intentkit/skills/heurist/image_generation_sdxl.py +12 -13
  198. intentkit/skills/heurist/schema.json +2 -2
  199. intentkit/skills/http/__init__.py +4 -15
  200. intentkit/skills/http/base.py +1 -7
  201. intentkit/skills/http/get.py +21 -16
  202. intentkit/skills/http/post.py +23 -18
  203. intentkit/skills/http/put.py +23 -18
  204. intentkit/skills/http/schema.json +4 -5
  205. intentkit/skills/lifi/__init__.py +8 -13
  206. intentkit/skills/lifi/base.py +1 -7
  207. intentkit/skills/lifi/schema.json +17 -8
  208. intentkit/skills/lifi/token_execute.py +36 -30
  209. intentkit/skills/lifi/token_quote.py +8 -10
  210. intentkit/skills/lifi/utils.py +104 -51
  211. intentkit/skills/moralis/__init__.py +6 -10
  212. intentkit/skills/moralis/api.py +6 -7
  213. intentkit/skills/moralis/base.py +5 -10
  214. intentkit/skills/moralis/fetch_chain_portfolio.py +10 -11
  215. intentkit/skills/moralis/fetch_nft_portfolio.py +22 -22
  216. intentkit/skills/moralis/fetch_solana_portfolio.py +11 -12
  217. intentkit/skills/moralis/fetch_wallet_portfolio.py +8 -9
  218. intentkit/skills/moralis/schema.json +7 -2
  219. intentkit/skills/morpho/__init__.py +52 -0
  220. intentkit/skills/morpho/base.py +11 -0
  221. intentkit/skills/morpho/morpho.svg +12 -0
  222. intentkit/skills/morpho/schema.json +73 -0
  223. intentkit/skills/nation/__init__.py +4 -9
  224. intentkit/skills/nation/base.py +5 -10
  225. intentkit/skills/nation/nft_check.py +3 -4
  226. intentkit/skills/nation/schema.json +4 -3
  227. intentkit/skills/onchain.py +23 -0
  228. intentkit/skills/openai/__init__.py +17 -18
  229. intentkit/skills/openai/base.py +10 -14
  230. intentkit/skills/openai/dalle_image_generation.py +3 -8
  231. intentkit/skills/openai/gpt_avatar_generator.py +102 -0
  232. intentkit/skills/openai/gpt_image_generation.py +4 -8
  233. intentkit/skills/openai/gpt_image_mini_generator.py +91 -0
  234. intentkit/skills/openai/gpt_image_to_image.py +4 -8
  235. intentkit/skills/openai/image_to_text.py +3 -7
  236. intentkit/skills/openai/schema.json +34 -3
  237. intentkit/skills/portfolio/__init__.py +11 -35
  238. intentkit/skills/portfolio/base.py +33 -19
  239. intentkit/skills/portfolio/schema.json +3 -5
  240. intentkit/skills/portfolio/token_balances.py +21 -21
  241. intentkit/skills/portfolio/wallet_approvals.py +17 -18
  242. intentkit/skills/portfolio/wallet_defi_positions.py +3 -3
  243. intentkit/skills/portfolio/wallet_history.py +31 -31
  244. intentkit/skills/portfolio/wallet_net_worth.py +13 -13
  245. intentkit/skills/portfolio/wallet_nfts.py +19 -19
  246. intentkit/skills/portfolio/wallet_profitability.py +18 -18
  247. intentkit/skills/portfolio/wallet_profitability_summary.py +5 -5
  248. intentkit/skills/portfolio/wallet_stats.py +3 -3
  249. intentkit/skills/portfolio/wallet_swaps.py +19 -19
  250. intentkit/skills/pyth/__init__.py +50 -0
  251. intentkit/skills/pyth/base.py +11 -0
  252. intentkit/skills/pyth/pyth.svg +6 -0
  253. intentkit/skills/pyth/schema.json +75 -0
  254. intentkit/skills/skills.toml +40 -0
  255. intentkit/skills/slack/__init__.py +5 -17
  256. intentkit/skills/slack/base.py +3 -9
  257. intentkit/skills/slack/get_channel.py +8 -8
  258. intentkit/skills/slack/get_message.py +9 -9
  259. intentkit/skills/slack/schedule_message.py +5 -5
  260. intentkit/skills/slack/schema.json +2 -2
  261. intentkit/skills/slack/send_message.py +3 -5
  262. intentkit/skills/supabase/__init__.py +7 -23
  263. intentkit/skills/supabase/base.py +9 -13
  264. intentkit/skills/supabase/delete_data.py +5 -6
  265. intentkit/skills/supabase/fetch_data.py +13 -14
  266. intentkit/skills/supabase/insert_data.py +5 -6
  267. intentkit/skills/supabase/invoke_function.py +7 -8
  268. intentkit/skills/supabase/schema.json +2 -3
  269. intentkit/skills/supabase/update_data.py +7 -8
  270. intentkit/skills/supabase/upsert_data.py +5 -6
  271. intentkit/skills/superfluid/__init__.py +53 -0
  272. intentkit/skills/superfluid/base.py +11 -0
  273. intentkit/skills/superfluid/schema.json +89 -0
  274. intentkit/skills/superfluid/superfluid.svg +6 -0
  275. intentkit/skills/system/__init__.py +7 -24
  276. intentkit/skills/system/add_autonomous_task.py +10 -12
  277. intentkit/skills/system/delete_autonomous_task.py +2 -2
  278. intentkit/skills/system/edit_autonomous_task.py +14 -18
  279. intentkit/skills/system/list_autonomous_tasks.py +3 -5
  280. intentkit/skills/system/read_agent_api_key.py +6 -4
  281. intentkit/skills/system/regenerate_agent_api_key.py +6 -4
  282. intentkit/skills/system/schema.json +6 -8
  283. intentkit/skills/tavily/__init__.py +3 -12
  284. intentkit/skills/tavily/base.py +4 -9
  285. intentkit/skills/tavily/schema.json +3 -5
  286. intentkit/skills/tavily/tavily_extract.py +2 -4
  287. intentkit/skills/tavily/tavily_search.py +4 -6
  288. intentkit/skills/token/__init__.py +5 -10
  289. intentkit/skills/token/base.py +7 -11
  290. intentkit/skills/token/erc20_transfers.py +19 -19
  291. intentkit/skills/token/schema.json +3 -6
  292. intentkit/skills/token/token_analytics.py +3 -3
  293. intentkit/skills/token/token_price.py +13 -13
  294. intentkit/skills/token/token_search.py +9 -9
  295. intentkit/skills/twitter/__init__.py +11 -35
  296. intentkit/skills/twitter/base.py +23 -35
  297. intentkit/skills/twitter/follow_user.py +3 -7
  298. intentkit/skills/twitter/get_mentions.py +6 -13
  299. intentkit/skills/twitter/get_timeline.py +5 -13
  300. intentkit/skills/twitter/get_user_by_username.py +3 -7
  301. intentkit/skills/twitter/get_user_tweets.py +6 -14
  302. intentkit/skills/twitter/like_tweet.py +3 -7
  303. intentkit/skills/twitter/post_tweet.py +23 -12
  304. intentkit/skills/twitter/reply_tweet.py +21 -12
  305. intentkit/skills/twitter/retweet.py +3 -7
  306. intentkit/skills/twitter/schema.json +1 -0
  307. intentkit/skills/twitter/search_tweets.py +5 -13
  308. intentkit/skills/unrealspeech/__init__.py +2 -7
  309. intentkit/skills/unrealspeech/base.py +2 -8
  310. intentkit/skills/unrealspeech/schema.json +2 -5
  311. intentkit/skills/unrealspeech/text_to_speech.py +8 -8
  312. intentkit/skills/venice_audio/__init__.py +98 -106
  313. intentkit/skills/venice_audio/base.py +117 -121
  314. intentkit/skills/venice_audio/input.py +41 -41
  315. intentkit/skills/venice_audio/schema.json +151 -152
  316. intentkit/skills/venice_audio/venice_audio.py +38 -21
  317. intentkit/skills/venice_image/__init__.py +147 -154
  318. intentkit/skills/venice_image/api.py +138 -138
  319. intentkit/skills/venice_image/base.py +185 -192
  320. intentkit/skills/venice_image/config.py +33 -35
  321. intentkit/skills/venice_image/image_enhance/image_enhance.py +2 -3
  322. intentkit/skills/venice_image/image_enhance/image_enhance_base.py +21 -23
  323. intentkit/skills/venice_image/image_enhance/image_enhance_input.py +38 -40
  324. intentkit/skills/venice_image/image_generation/image_generation_base.py +9 -9
  325. intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -26
  326. intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -27
  327. intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -26
  328. intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -158
  329. intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -26
  330. intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -26
  331. intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -28
  332. intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -28
  333. intentkit/skills/venice_image/image_upscale/image_upscale.py +3 -3
  334. intentkit/skills/venice_image/image_upscale/image_upscale_base.py +21 -23
  335. intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -22
  336. intentkit/skills/venice_image/image_vision/image_vision.py +2 -2
  337. intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -17
  338. intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -9
  339. intentkit/skills/venice_image/schema.json +267 -267
  340. intentkit/skills/venice_image/utils.py +77 -78
  341. intentkit/skills/web_scraper/__init__.py +5 -18
  342. intentkit/skills/web_scraper/base.py +21 -7
  343. intentkit/skills/web_scraper/document_indexer.py +7 -6
  344. intentkit/skills/web_scraper/schema.json +2 -6
  345. intentkit/skills/web_scraper/scrape_and_index.py +15 -15
  346. intentkit/skills/web_scraper/utils.py +62 -63
  347. intentkit/skills/web_scraper/website_indexer.py +17 -19
  348. intentkit/skills/weth/__init__.py +49 -0
  349. intentkit/skills/weth/base.py +11 -0
  350. intentkit/skills/weth/schema.json +58 -0
  351. intentkit/skills/weth/weth.svg +6 -0
  352. intentkit/skills/wow/__init__.py +51 -0
  353. intentkit/skills/wow/base.py +11 -0
  354. intentkit/skills/wow/schema.json +89 -0
  355. intentkit/skills/wow/wow.svg +7 -0
  356. intentkit/skills/x402/__init__.py +61 -0
  357. intentkit/skills/x402/ask_agent.py +98 -0
  358. intentkit/skills/x402/base.py +99 -0
  359. intentkit/skills/x402/http_request.py +117 -0
  360. intentkit/skills/x402/schema.json +45 -0
  361. intentkit/skills/x402/x402.webp +0 -0
  362. intentkit/skills/xmtp/__init__.py +4 -15
  363. intentkit/skills/xmtp/base.py +61 -2
  364. intentkit/skills/xmtp/price.py +18 -13
  365. intentkit/skills/xmtp/schema.json +69 -71
  366. intentkit/skills/xmtp/swap.py +22 -25
  367. intentkit/skills/xmtp/transfer.py +71 -32
  368. intentkit/utils/chain.py +3 -3
  369. intentkit/utils/error.py +14 -1
  370. intentkit/utils/logging.py +2 -4
  371. intentkit/utils/s3.py +59 -7
  372. intentkit/utils/schema.py +100 -0
  373. intentkit/utils/slack_alert.py +7 -8
  374. {intentkit-0.6.13.dev2.dist-info → intentkit-0.8.17.dist-info}/METADATA +14 -16
  375. intentkit-0.8.17.dist-info/RECORD +466 -0
  376. intentkit/abstracts/exception.py +0 -9
  377. intentkit/core/skill.py +0 -200
  378. intentkit/models/generator.py +0 -347
  379. intentkit/skills/cdp/get_balance.py +0 -110
  380. intentkit/skills/cdp/swap.py +0 -121
  381. intentkit/skills/moralis/tests/__init__.py +0 -0
  382. intentkit/skills/moralis/tests/test_wallet.py +0 -511
  383. intentkit-0.6.13.dev2.dist-info/RECORD +0 -409
  384. {intentkit-0.6.13.dev2.dist-info → intentkit-0.8.17.dist-info}/WHEEL +0 -0
  385. {intentkit-0.6.13.dev2.dist-info → intentkit-0.8.17.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,9 @@
1
- from typing import Literal, Type
1
+ from typing import Literal
2
2
 
3
+ from langchain_core.tools.base import ToolException
3
4
  from pydantic import BaseModel, Field
4
5
 
5
- from intentkit.clients.cdp import get_origin_cdp_client
6
+ from intentkit.clients.cdp import get_cdp_client
6
7
  from intentkit.skills.xmtp.base import XmtpBaseTool
7
8
 
8
9
 
@@ -21,9 +22,9 @@ class XmtpGetSwapPrice(XmtpBaseTool):
21
22
  """Skill for fetching indicative swap price using CDP SDK."""
22
23
 
23
24
  name: str = "xmtp_get_swap_price"
24
- description: str = "Get an indicative swap price/quote for token pair and amount on Base networks using CDP."
25
+ description: str = "Get an indicative swap price/quote for token pair and amount on Ethereum, Base, Arbitrum, and Optimism mainnet networks using CDP."
25
26
  response_format: Literal["content", "content_and_artifact"] = "content"
26
- args_schema: Type[BaseModel] = SwapPriceInput
27
+ args_schema: type[BaseModel] = SwapPriceInput
27
28
 
28
29
  async def _arun(
29
30
  self,
@@ -35,18 +36,22 @@ class XmtpGetSwapPrice(XmtpBaseTool):
35
36
  context = self.get_context()
36
37
  agent = context.agent
37
38
 
38
- if agent.network_id not in ("base-mainnet", "base-sepolia"):
39
- raise ValueError(
40
- f"Swap price only supported on base-mainnet or base-sepolia. Current: {agent.network_id}"
39
+ # Only support mainnet networks for price and swap
40
+ supported_networks = [
41
+ "ethereum-mainnet",
42
+ "base-mainnet",
43
+ "arbitrum-mainnet",
44
+ "optimism-mainnet",
45
+ ]
46
+ if agent.network_id not in supported_networks:
47
+ raise ToolException(
48
+ f"Swap price only supported on {', '.join(supported_networks)}. Current: {agent.network_id}"
41
49
  )
42
50
 
43
- network_for_cdp = {
44
- "base-mainnet": "base",
45
- "base-sepolia": "base-sepolia",
46
- }[agent.network_id]
51
+ network_for_cdp = self.get_cdp_network(agent.network_id)
47
52
 
48
- cdp_client = get_origin_cdp_client(self.skill_store)
49
- # 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
50
55
  price = await cdp_client.evm.get_swap_price(
51
56
  from_token=from_token,
52
57
  to_token=to_token,
@@ -1,75 +1,73 @@
1
1
  {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "type": "object",
4
- "title": "XMTP",
5
- "description": "Use this skill only if you want make an XMTP Agent. XMTP protocol skills for creating blockchain transaction requests that can be sent to users for signing",
6
- "x-icon": "https://ai.service.crestal.dev/skills/xmtp/xmtp.png",
7
- "x-tags": [
8
- "XMTP",
9
- "Blockchain",
10
- "Transactions",
11
- "Web3",
12
- "Base"
13
- ],
14
- "properties": {
15
- "enabled": {
16
- "type": "boolean",
17
- "title": "Enabled",
18
- "description": "Whether this skill is enabled",
19
- "default": false
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "title": "XMTP",
5
+ "description": "Use this skill only if you want make an XMTP Agent. XMTP protocol skills for creating blockchain transaction requests that can be sent to users for signing",
6
+ "x-icon": "https://ai.service.crestal.dev/skills/xmtp/xmtp.png",
7
+ "x-tags": [
8
+ "Communication",
9
+ "Crypto",
10
+ "DeFi"
11
+ ],
12
+ "properties": {
13
+ "enabled": {
14
+ "type": "boolean",
15
+ "title": "Enabled",
16
+ "description": "Whether this skill is enabled",
17
+ "default": false
18
+ },
19
+ "states": {
20
+ "type": "object",
21
+ "properties": {
22
+ "xmtp_transfer": {
23
+ "type": "string",
24
+ "title": "XMTP Transfer",
25
+ "enum": [
26
+ "disabled",
27
+ "public",
28
+ "private"
29
+ ],
30
+ "x-enum-title": [
31
+ "Disabled",
32
+ "Agent Owner + All Users",
33
+ "Agent Owner Only"
34
+ ],
35
+ "description": "Create XMTP transaction requests for transferring ETH or ERC20 tokens on Base mainnet. Supports both native ETH transfers and ERC20 token transfers. Generates wallet_sendCalls transaction data that users can sign.",
36
+ "default": "disabled"
20
37
  },
21
- "states": {
22
- "type": "object",
23
- "properties": {
24
- "xmtp_transfer": {
25
- "type": "string",
26
- "title": "XMTP Transfer",
27
- "enum": [
28
- "disabled",
29
- "public",
30
- "private"
31
- ],
32
- "x-enum-title": [
33
- "Disabled",
34
- "Agent Owner + All Users",
35
- "Agent Owner Only"
36
- ],
37
- "description": "Create XMTP transaction requests for transferring ETH or ERC20 tokens on Base mainnet. Supports both native ETH transfers and ERC20 token transfers. Generates wallet_sendCalls transaction data that users can sign.",
38
- "default": "disabled"
39
- },
40
- "xmtp_swap": {
41
- "type": "string",
42
- "title": "XMTP Swap",
43
- "enum": [
44
- "disabled",
45
- "public",
46
- "private"
47
- ],
48
- "x-enum-title": [
49
- "Disabled",
50
- "Agent Owner + All Users",
51
- "Agent Owner Only"
52
- ],
53
- "description": "Create XMTP transaction requests for swapping tokens on Base using CDP swap quote. Returns a wallet_sendCalls payload that can include an optional approval call and the swap call. Only supports base-mainnet and base-sepolia.",
54
- "default": "disabled"
55
- },
56
- "xmtp_get_swap_price": {
57
- "type": "string",
58
- "title": "XMTP Get Swap Price",
59
- "enum": [
60
- "disabled",
61
- "public",
62
- "private"
63
- ],
64
- "x-enum-title": [
65
- "Disabled",
66
- "Agent Owner + All Users",
67
- "Agent Owner Only"
68
- ],
69
- "description": "Get an indicative swap price/quote for token pair and amount on Base networks using CDP. Provides estimated output amounts for token swaps without creating transactions.",
70
- "default": "disabled"
71
- }
72
- }
38
+ "xmtp_swap": {
39
+ "type": "string",
40
+ "title": "XMTP Swap",
41
+ "enum": [
42
+ "disabled",
43
+ "public",
44
+ "private"
45
+ ],
46
+ "x-enum-title": [
47
+ "Disabled",
48
+ "Agent Owner + All Users",
49
+ "Agent Owner Only"
50
+ ],
51
+ "description": "Create XMTP transaction requests for swapping tokens on Base using CDP swap quote. Returns a wallet_sendCalls payload that can include an optional approval call and the swap call. Only supports base-mainnet and base-sepolia.",
52
+ "default": "disabled"
53
+ },
54
+ "xmtp_get_swap_price": {
55
+ "type": "string",
56
+ "title": "XMTP Get Swap Price",
57
+ "enum": [
58
+ "disabled",
59
+ "public",
60
+ "private"
61
+ ],
62
+ "x-enum-title": [
63
+ "Disabled",
64
+ "Agent Owner + All Users",
65
+ "Agent Owner Only"
66
+ ],
67
+ "description": "Get an indicative swap price/quote for token pair and amount on Base networks using CDP. Provides estimated output amounts for token swaps without creating transactions.",
68
+ "default": "disabled"
73
69
  }
70
+ }
74
71
  }
75
- }
72
+ }
73
+ }
@@ -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
 
@@ -33,16 +31,16 @@ class XmtpSwap(XmtpBaseTool):
33
31
 
34
32
  Generates a wallet_sendCalls transaction request to perform a token swap.
35
33
  May include an ERC20 approval call followed by the router swap call.
36
- Supports Base mainnet and Base Sepolia testnet.
34
+ Supports Ethereum, Polygon, Base, Arbitrum, and Optimism networks (both mainnet and testnet).
37
35
  """
38
36
 
39
37
  name: str = "xmtp_swap"
40
38
  description: str = (
41
- "Create an XMTP transaction request for swapping tokens on Base using CDP swap quote. "
39
+ "Create an XMTP transaction request for swapping tokens using CDP swap quote. "
42
40
  "Returns a wallet_sendCalls payload that can include an optional approval call and the swap call. "
43
- "Only supports base-mainnet and base-sepolia."
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
@@ -87,36 +85,35 @@ class XmtpSwap(XmtpBaseTool):
87
85
  context = self.get_context()
88
86
  agent = context.agent
89
87
 
90
- # ChainId mapping for XMTP wallet_sendCalls
91
- chain_id_hex_by_network = {
92
- "base-mainnet": "0x2105", # 8453
93
- "base-sepolia": "0x14A34", # 84532
94
- }
95
-
96
- if agent.network_id not in chain_id_hex_by_network:
88
+ # Only support mainnet networks for swap
89
+ supported_networks = [
90
+ "ethereum-mainnet",
91
+ "base-mainnet",
92
+ "arbitrum-mainnet",
93
+ "optimism-mainnet",
94
+ ]
95
+ if agent.network_id not in supported_networks:
97
96
  raise ValueError(
98
- f"XMTP swap only supports base-mainnet or base-sepolia. Current agent network: {agent.network_id}"
97
+ f"Swap only supported on {', '.join(supported_networks)}. Current: {agent.network_id}"
99
98
  )
100
99
 
101
- chain_id_hex = chain_id_hex_by_network[agent.network_id]
100
+ # Validate network and get chain ID
101
+ chain_id_hex = self.validate_network_and_get_chain_id(agent.network_id, "swap")
102
102
 
103
- # CDP network mapping for swap quote API
103
+ # Get CDP network name
104
104
  # Reference: CDP SDK examples for swap quote and price
105
105
  # https://github.com/coinbase/cdp-sdk/blob/main/examples/python/evm/swaps/create_swap_quote.py
106
- network_for_cdp = {
107
- "base-mainnet": "base",
108
- "base-sepolia": "base-sepolia",
109
- }[agent.network_id]
106
+ network_for_cdp = self.get_cdp_network(agent.network_id)
110
107
 
111
- # Get CDP client from global origin helper (server-side credentials)
112
- cdp_client = get_origin_cdp_client(self.skill_store)
108
+ # Get CDP client from the global helper (server-side credentials)
109
+ cdp_client = get_cdp_client()
113
110
 
114
111
  # Call CDP to create swap quote and extract call datas
115
112
  # Be permissive with response shape across SDK versions
116
113
  try:
117
114
  # Attempt the canonical method per CDP SDK examples
118
115
  # create_swap_quote(from_token, to_token, from_amount, network, taker, slippage_bps, signer_address)
119
- # 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
120
117
  quote = await cdp_client.evm.create_swap_quote(
121
118
  from_token=from_token,
122
119
  to_token=to_token,
@@ -1,6 +1,5 @@
1
- from typing import List, Optional, Tuple, Type
2
-
3
1
  from pydantic import BaseModel, Field
2
+ from web3.exceptions import ContractLogicError
4
3
 
5
4
  from intentkit.models.chat import ChatMessageAttachment, ChatMessageAttachmentType
6
5
  from intentkit.skills.xmtp.base import XmtpBaseTool
@@ -12,13 +11,10 @@ class TransferInput(BaseModel):
12
11
  from_address: str = Field(description="The sender address for the transfer")
13
12
  to_address: str = Field(description="The recipient address for the transfer")
14
13
  amount: str = Field(
15
- description="The amount to transfer (as string to handle large numbers)"
16
- )
17
- decimals: int = Field(
18
- description="Number of decimal places for the token (18 for ETH, varies for ERC20 tokens)"
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."
19
15
  )
20
- currency: str = Field(description="Currency symbol (e.g., 'ETH', 'USDC', 'DAI')")
21
- token_contract_address: Optional[str] = Field(
16
+ currency: str = Field(description="Currency symbol (e.g., 'ETH', 'USDC', 'NATION')")
17
+ token_contract_address: str | None = Field(
22
18
  default=None,
23
19
  description="Token contract address for ERC20 transfers. Leave empty for ETH transfers.",
24
20
  )
@@ -28,36 +24,29 @@ class XmtpTransfer(XmtpBaseTool):
28
24
  """Skill for creating XMTP transfer transactions."""
29
25
 
30
26
  name: str = "xmtp_transfer"
31
- description: str = """Create an XMTP transaction request for transferring ETH or ERC20 tokens on Base mainnet.
32
-
27
+ description: str = """Create an XMTP transaction request for transferring ETH or ERC20 tokens.
33
28
  This skill generates a wallet_sendCalls transaction request according to XMTP protocol
34
- that can be sent to users for signing. The transaction can transfer:
35
- - ETH (when token_contract_address is not provided)
36
- - ERC20 tokens (when token_contract_address is provided)
37
-
38
- Only supports Base mainnet network.
29
+ that can be sent to users for signing.
30
+ Supports Ethereum, Polygon, Base, Arbitrum, and Optimism networks (both mainnet and testnet).
39
31
  """
40
- args_schema: Type[BaseModel] = TransferInput
32
+ args_schema: type[BaseModel] = TransferInput
41
33
 
42
34
  async def _arun(
43
35
  self,
44
36
  from_address: str,
45
37
  to_address: str,
46
38
  amount: str,
47
- decimals: int,
48
39
  currency: str,
49
- token_contract_address: Optional[str],
50
- ) -> Tuple[str, List[ChatMessageAttachment]]:
40
+ token_contract_address: str | None,
41
+ ) -> tuple[str, list[ChatMessageAttachment]]:
51
42
  """Create an XMTP transfer transaction request.
52
43
 
53
44
  Args:
54
45
  from_address: The sender address
55
46
  to_address: The recipient address
56
47
  amount: Amount to transfer
57
- decimals: Token decimals
58
48
  currency: Currency symbol
59
49
  token_contract_address: Token contract address (None for ETH)
60
- config: LangChain runnable config
61
50
 
62
51
  Returns:
63
52
  Tuple of (content_message, list_of_attachments)
@@ -66,19 +55,69 @@ class XmtpTransfer(XmtpBaseTool):
66
55
  context = self.get_context()
67
56
  agent = context.agent
68
57
 
69
- # ChainId mapping for XMTP wallet_sendCalls
70
- chain_id_hex_by_network = {
71
- "base-mainnet": "0x2105", # 8453
72
- "base-sepolia": "0x14A34", # 84532
73
- }
58
+ # Validate network and get chain ID
59
+ chain_id_hex = self.validate_network_and_get_chain_id(
60
+ agent.network_id, "transfer"
61
+ )
74
62
 
75
- if agent.network_id not in chain_id_hex_by_network:
76
- raise ValueError(
77
- f"XMTP transfer only supports base-mainnet or base-sepolia network. "
78
- f"Current agent network: {agent.network_id}"
79
- )
63
+ # Validate token contract and get decimals
64
+ if token_contract_address:
65
+ # Validate ERC20 contract and get token info
66
+ web3 = self.web3_client()
80
67
 
81
- chain_id_hex = chain_id_hex_by_network[agent.network_id]
68
+ # ERC20 ABI for symbol() and decimals() functions
69
+ erc20_abi = [
70
+ {
71
+ "constant": True,
72
+ "inputs": [],
73
+ "name": "symbol",
74
+ "outputs": [{"name": "", "type": "string"}],
75
+ "type": "function",
76
+ },
77
+ {
78
+ "constant": True,
79
+ "inputs": [],
80
+ "name": "decimals",
81
+ "outputs": [{"name": "", "type": "uint8"}],
82
+ "type": "function",
83
+ },
84
+ ]
85
+
86
+ try:
87
+ # Create contract instance
88
+ contract = web3.eth.contract(
89
+ address=web3.to_checksum_address(token_contract_address),
90
+ abi=erc20_abi,
91
+ )
92
+
93
+ # Get token symbol and decimals
94
+ token_symbol = contract.functions.symbol().call()
95
+ decimals = contract.functions.decimals().call()
96
+
97
+ # Validate symbol matches currency (case insensitive)
98
+ if token_symbol.upper() != currency.upper():
99
+ raise ValueError(
100
+ f"Token symbol mismatch: contract symbol is '{token_symbol}', "
101
+ f"but currency parameter is '{currency}'"
102
+ )
103
+
104
+ except ContractLogicError:
105
+ raise ValueError(
106
+ f"Invalid ERC20 contract address: {token_contract_address}. "
107
+ "The address does not point to a valid ERC20 token contract."
108
+ )
109
+ except Exception as e:
110
+ raise ValueError(
111
+ f"Failed to validate ERC20 contract {token_contract_address}: {str(e)}"
112
+ )
113
+ else:
114
+ # For ETH transfers, use 18 decimals
115
+ decimals = 18
116
+ # Validate currency is ETH for native transfers
117
+ if currency.upper() != "ETH":
118
+ raise ValueError(
119
+ f"For native transfers, currency must be 'ETH', got '{currency}'"
120
+ )
82
121
 
83
122
  # Calculate amount in smallest unit (wei for ETH, token units for ERC20)
84
123
  amount_int = int(float(amount) * (10**decimals))
intentkit/utils/chain.py CHANGED
@@ -422,9 +422,9 @@ class QuicknodeChainProvider(ChainProvider):
422
422
  )
423
423
 
424
424
  except httpx.HTTPStatusError as http_err:
425
- raise (f"Quicknode API HTTP Error: {http_err}")
425
+ raise Exception(f"Quicknode API HTTP Error: {http_err}")
426
426
  except httpx.RequestError as req_err:
427
- raise (f"Quicknode API Request Error: {req_err}")
427
+ raise Exception(f"Quicknode API Request Error: {req_err}")
428
428
  except (
429
429
  KeyError,
430
430
  TypeError,
@@ -433,4 +433,4 @@ class QuicknodeChainProvider(ChainProvider):
433
433
  f"Error processing QuickNode API response: {e}. Check the API response format."
434
434
  )
435
435
  except Exception as e:
436
- raise (f"Quicknode API An unexpected error occurred: {e}")
436
+ raise Exception(f"Quicknode API An unexpected error occurred: {e}")
intentkit/utils/error.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import 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
@@ -11,8 +11,21 @@ from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
+ # error messages in agent system message response
15
+
16
+
17
+ class RateLimitExceeded(Exception):
18
+ """Rate limit exceeded"""
19
+
20
+ def __init__(self, message: str | None = "Rate limit exceeded"):
21
+ self.message = message
22
+ super().__init__(self.message)
23
+
14
24
 
15
25
  class IntentKitAPIError(Exception):
26
+ """All 3 parameters: status_code, key and message is required.
27
+ The key is PascalCase string, to allow the frontend to test errors."""
28
+
16
29
  def __init__(self, status_code: int, key: str, message: str):
17
30
  self.key = key
18
31
  self.message = message
@@ -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.
@@ -182,11 +181,64 @@ class FileType(str, Enum):
182
181
  PDF = "pdf"
183
182
 
184
183
 
184
+ async def store_file(
185
+ content: bytes,
186
+ key: str,
187
+ content_type: str | None = None,
188
+ size: int | None = None,
189
+ ) -> str:
190
+ """Store raw file bytes with automatic content type detection."""
191
+ if not _client or not _bucket or not _prefix or not _cdn_url:
192
+ logger.info("S3 not initialized. Cannot store file bytes.")
193
+ return ""
194
+
195
+ if not content:
196
+ raise ValueError("File content cannot be empty")
197
+
198
+ actual_size = len(content)
199
+ if size is not None and size != actual_size:
200
+ raise ValueError(
201
+ f"Provided size {size} does not match actual content size {actual_size} bytes"
202
+ )
203
+
204
+ effective_size = size if size is not None else actual_size
205
+
206
+ detected_content_type = content_type
207
+ if not detected_content_type:
208
+ kind = filetype.guess(content)
209
+ detected_content_type = (
210
+ kind.mime if kind and kind.mime else "application/octet-stream"
211
+ )
212
+
213
+ prefixed_key = f"{_prefix}{key}"
214
+ file_obj = BytesIO(content)
215
+
216
+ logger.info(
217
+ "Uploading file to S3 with content type %s and size %s bytes",
218
+ detected_content_type,
219
+ effective_size,
220
+ )
221
+
222
+ _client.upload_fileobj(
223
+ file_obj,
224
+ _bucket,
225
+ prefixed_key,
226
+ ExtraArgs={
227
+ "ContentType": detected_content_type,
228
+ "ContentDisposition": "inline",
229
+ },
230
+ )
231
+
232
+ cdn_url = f"{_cdn_url}/{prefixed_key}"
233
+ logger.info("File uploaded successfully to %s", cdn_url)
234
+ return cdn_url
235
+
236
+
185
237
  async def store_file_bytes(
186
238
  file_bytes: bytes,
187
239
  key: str,
188
240
  file_type: FileType,
189
- size_limit_bytes: Optional[int] = None,
241
+ size_limit_bytes: int | None = None,
190
242
  ) -> str:
191
243
  """
192
244
  Store raw file bytes (image, video, sound, pdf) to S3.