intentkit 0.7.5.dev3__py3-none-any.whl → 0.8.34.dev7__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.
Files changed (393) hide show
  1. intentkit/MANIFEST.in +14 -0
  2. intentkit/README.md +88 -0
  3. intentkit/__init__.py +6 -4
  4. intentkit/abstracts/agent.py +4 -5
  5. intentkit/abstracts/engine.py +5 -5
  6. intentkit/abstracts/graph.py +15 -8
  7. intentkit/abstracts/skill.py +6 -144
  8. intentkit/abstracts/twitter.py +4 -5
  9. intentkit/clients/__init__.py +9 -2
  10. intentkit/clients/cdp.py +129 -153
  11. intentkit/{utils → clients}/s3.py +109 -34
  12. intentkit/clients/twitter.py +83 -62
  13. intentkit/clients/web3.py +4 -7
  14. intentkit/config/config.py +123 -90
  15. intentkit/core/account_checking.py +802 -0
  16. intentkit/core/agent.py +313 -498
  17. intentkit/core/asset.py +267 -0
  18. intentkit/core/chat.py +5 -3
  19. intentkit/core/client.py +1 -1
  20. intentkit/core/credit.py +49 -41
  21. intentkit/core/draft.py +201 -0
  22. intentkit/core/draft_chat.py +118 -0
  23. intentkit/core/engine.py +378 -287
  24. intentkit/core/manager/__init__.py +25 -0
  25. intentkit/core/manager/engine.py +220 -0
  26. intentkit/core/manager/service.py +172 -0
  27. intentkit/core/manager/skills.py +178 -0
  28. intentkit/core/middleware.py +231 -0
  29. intentkit/core/prompt.py +74 -114
  30. intentkit/core/scheduler.py +143 -0
  31. intentkit/core/statistics.py +168 -0
  32. intentkit/models/agent.py +931 -518
  33. intentkit/models/agent_data.py +165 -106
  34. intentkit/models/agent_schema.json +38 -251
  35. intentkit/models/app_setting.py +15 -13
  36. intentkit/models/chat.py +86 -140
  37. intentkit/models/credit.py +182 -162
  38. intentkit/models/db.py +42 -23
  39. intentkit/models/db_mig.py +120 -3
  40. intentkit/models/draft.py +222 -0
  41. intentkit/models/llm.csv +31 -0
  42. intentkit/models/llm.py +262 -370
  43. intentkit/models/redis.py +6 -4
  44. intentkit/models/skill.py +222 -101
  45. intentkit/models/skills.csv +173 -0
  46. intentkit/models/team.py +189 -0
  47. intentkit/models/user.py +103 -31
  48. intentkit/skills/acolyt/__init__.py +2 -9
  49. intentkit/skills/acolyt/ask.py +3 -4
  50. intentkit/skills/acolyt/base.py +4 -9
  51. intentkit/skills/acolyt/schema.json +4 -3
  52. intentkit/skills/aixbt/__init__.py +2 -13
  53. intentkit/skills/aixbt/base.py +1 -7
  54. intentkit/skills/aixbt/projects.py +14 -15
  55. intentkit/skills/aixbt/schema.json +4 -4
  56. intentkit/skills/allora/__init__.py +2 -9
  57. intentkit/skills/allora/base.py +4 -9
  58. intentkit/skills/allora/price.py +3 -4
  59. intentkit/skills/allora/schema.json +3 -2
  60. intentkit/skills/base.py +241 -41
  61. intentkit/skills/basename/__init__.py +51 -0
  62. intentkit/skills/basename/base.py +11 -0
  63. intentkit/skills/basename/basename.svg +11 -0
  64. intentkit/skills/basename/schema.json +58 -0
  65. intentkit/skills/carv/__init__.py +115 -121
  66. intentkit/skills/carv/base.py +184 -185
  67. intentkit/skills/carv/fetch_news.py +3 -3
  68. intentkit/skills/carv/onchain_query.py +4 -4
  69. intentkit/skills/carv/schema.json +134 -137
  70. intentkit/skills/carv/token_info_and_price.py +6 -6
  71. intentkit/skills/casino/__init__.py +4 -15
  72. intentkit/skills/casino/base.py +1 -7
  73. intentkit/skills/casino/deck_draw.py +5 -8
  74. intentkit/skills/casino/deck_shuffle.py +6 -6
  75. intentkit/skills/casino/dice_roll.py +2 -4
  76. intentkit/skills/casino/schema.json +0 -1
  77. intentkit/skills/cdp/__init__.py +22 -84
  78. intentkit/skills/cdp/base.py +1 -7
  79. intentkit/skills/cdp/schema.json +11 -314
  80. intentkit/skills/chainlist/__init__.py +2 -7
  81. intentkit/skills/chainlist/base.py +1 -7
  82. intentkit/skills/chainlist/chain_lookup.py +18 -18
  83. intentkit/skills/chainlist/schema.json +3 -5
  84. intentkit/skills/common/__init__.py +2 -9
  85. intentkit/skills/common/base.py +1 -7
  86. intentkit/skills/common/current_time.py +1 -2
  87. intentkit/skills/common/schema.json +2 -2
  88. intentkit/skills/cookiefun/__init__.py +6 -9
  89. intentkit/skills/cookiefun/base.py +2 -7
  90. intentkit/skills/cookiefun/get_account_details.py +7 -7
  91. intentkit/skills/cookiefun/get_account_feed.py +19 -19
  92. intentkit/skills/cookiefun/get_account_smart_followers.py +7 -7
  93. intentkit/skills/cookiefun/get_sectors.py +3 -3
  94. intentkit/skills/cookiefun/schema.json +1 -3
  95. intentkit/skills/cookiefun/search_accounts.py +9 -9
  96. intentkit/skills/cryptocompare/__init__.py +7 -24
  97. intentkit/skills/cryptocompare/api.py +2 -3
  98. intentkit/skills/cryptocompare/base.py +10 -24
  99. intentkit/skills/cryptocompare/fetch_news.py +4 -5
  100. intentkit/skills/cryptocompare/fetch_price.py +6 -7
  101. intentkit/skills/cryptocompare/fetch_top_exchanges.py +4 -5
  102. intentkit/skills/cryptocompare/fetch_top_market_cap.py +4 -5
  103. intentkit/skills/cryptocompare/fetch_top_volume.py +4 -5
  104. intentkit/skills/cryptocompare/fetch_trading_signals.py +5 -6
  105. intentkit/skills/cryptocompare/schema.json +3 -3
  106. intentkit/skills/cryptopanic/__init__.py +7 -10
  107. intentkit/skills/cryptopanic/base.py +51 -55
  108. intentkit/skills/cryptopanic/fetch_crypto_news.py +4 -8
  109. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +5 -7
  110. intentkit/skills/cryptopanic/schema.json +105 -103
  111. intentkit/skills/dapplooker/__init__.py +2 -9
  112. intentkit/skills/dapplooker/base.py +4 -9
  113. intentkit/skills/dapplooker/dapplooker_token_data.py +7 -7
  114. intentkit/skills/dapplooker/schema.json +3 -5
  115. intentkit/skills/defillama/__init__.py +24 -74
  116. intentkit/skills/defillama/api.py +6 -9
  117. intentkit/skills/defillama/base.py +8 -19
  118. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +8 -10
  119. intentkit/skills/defillama/coins/fetch_block.py +6 -8
  120. intentkit/skills/defillama/coins/fetch_current_prices.py +8 -10
  121. intentkit/skills/defillama/coins/fetch_first_price.py +7 -9
  122. intentkit/skills/defillama/coins/fetch_historical_prices.py +9 -11
  123. intentkit/skills/defillama/coins/fetch_price_chart.py +9 -11
  124. intentkit/skills/defillama/coins/fetch_price_percentage.py +7 -9
  125. intentkit/skills/defillama/config/chains.py +1 -3
  126. intentkit/skills/defillama/fees/fetch_fees_overview.py +24 -26
  127. intentkit/skills/defillama/schema.json +5 -1
  128. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +16 -18
  129. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +8 -10
  130. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +5 -7
  131. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +7 -9
  132. intentkit/skills/defillama/tests/api_integration.test.py +1 -1
  133. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +4 -6
  134. intentkit/skills/defillama/tvl/fetch_chains.py +9 -11
  135. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +4 -6
  136. intentkit/skills/defillama/tvl/fetch_protocol.py +32 -38
  137. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +3 -5
  138. intentkit/skills/defillama/tvl/fetch_protocols.py +37 -45
  139. intentkit/skills/defillama/volumes/fetch_dex_overview.py +42 -48
  140. intentkit/skills/defillama/volumes/fetch_dex_summary.py +35 -37
  141. intentkit/skills/defillama/volumes/fetch_options_overview.py +24 -28
  142. intentkit/skills/defillama/yields/fetch_pool_chart.py +10 -12
  143. intentkit/skills/defillama/yields/fetch_pools.py +26 -30
  144. intentkit/skills/dexscreener/__init__.py +97 -102
  145. intentkit/skills/dexscreener/base.py +125 -130
  146. intentkit/skills/dexscreener/get_pair_info.py +4 -5
  147. intentkit/skills/dexscreener/get_token_pairs.py +4 -5
  148. intentkit/skills/dexscreener/get_tokens_info.py +7 -8
  149. intentkit/skills/dexscreener/model/search_token_response.py +80 -82
  150. intentkit/skills/dexscreener/schema.json +91 -93
  151. intentkit/skills/dexscreener/search_token.py +182 -184
  152. intentkit/skills/dexscreener/utils.py +15 -14
  153. intentkit/skills/dune_analytics/__init__.py +7 -9
  154. intentkit/skills/dune_analytics/base.py +48 -52
  155. intentkit/skills/dune_analytics/fetch_kol_buys.py +5 -7
  156. intentkit/skills/dune_analytics/fetch_nation_metrics.py +6 -8
  157. intentkit/skills/dune_analytics/schema.json +104 -99
  158. intentkit/skills/elfa/__init__.py +5 -18
  159. intentkit/skills/elfa/base.py +10 -14
  160. intentkit/skills/elfa/mention.py +19 -21
  161. intentkit/skills/elfa/schema.json +3 -2
  162. intentkit/skills/elfa/stats.py +4 -4
  163. intentkit/skills/elfa/tokens.py +12 -12
  164. intentkit/skills/elfa/utils.py +26 -28
  165. intentkit/skills/enso/__init__.py +11 -31
  166. intentkit/skills/enso/base.py +54 -35
  167. intentkit/skills/enso/best_yield.py +16 -24
  168. intentkit/skills/enso/networks.py +6 -11
  169. intentkit/skills/enso/prices.py +11 -13
  170. intentkit/skills/enso/route.py +34 -38
  171. intentkit/skills/enso/schema.json +3 -2
  172. intentkit/skills/enso/tokens.py +29 -38
  173. intentkit/skills/enso/wallet.py +76 -191
  174. intentkit/skills/erc20/__init__.py +50 -0
  175. intentkit/skills/erc20/base.py +11 -0
  176. intentkit/skills/erc20/erc20.svg +5 -0
  177. intentkit/skills/erc20/schema.json +74 -0
  178. intentkit/skills/erc721/__init__.py +53 -0
  179. intentkit/skills/erc721/base.py +11 -0
  180. intentkit/skills/erc721/erc721.svg +5 -0
  181. intentkit/skills/erc721/schema.json +90 -0
  182. intentkit/skills/firecrawl/__init__.py +5 -18
  183. intentkit/skills/firecrawl/base.py +4 -9
  184. intentkit/skills/firecrawl/clear.py +4 -8
  185. intentkit/skills/firecrawl/crawl.py +19 -19
  186. intentkit/skills/firecrawl/query.py +4 -3
  187. intentkit/skills/firecrawl/schema.json +2 -6
  188. intentkit/skills/firecrawl/scrape.py +17 -22
  189. intentkit/skills/firecrawl/utils.py +50 -42
  190. intentkit/skills/github/__init__.py +2 -7
  191. intentkit/skills/github/base.py +1 -7
  192. intentkit/skills/github/github_search.py +1 -2
  193. intentkit/skills/github/schema.json +3 -4
  194. intentkit/skills/heurist/__init__.py +8 -27
  195. intentkit/skills/heurist/base.py +4 -9
  196. intentkit/skills/heurist/image_generation_animagine_xl.py +13 -15
  197. intentkit/skills/heurist/image_generation_arthemy_comics.py +13 -15
  198. intentkit/skills/heurist/image_generation_arthemy_real.py +13 -15
  199. intentkit/skills/heurist/image_generation_braindance.py +13 -15
  200. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +13 -15
  201. intentkit/skills/heurist/image_generation_flux_1_dev.py +13 -15
  202. intentkit/skills/heurist/image_generation_sdxl.py +13 -15
  203. intentkit/skills/heurist/schema.json +2 -2
  204. intentkit/skills/http/__init__.py +4 -15
  205. intentkit/skills/http/base.py +1 -7
  206. intentkit/skills/http/get.py +21 -16
  207. intentkit/skills/http/post.py +23 -18
  208. intentkit/skills/http/put.py +23 -18
  209. intentkit/skills/http/schema.json +4 -5
  210. intentkit/skills/lifi/__init__.py +8 -13
  211. intentkit/skills/lifi/base.py +3 -9
  212. intentkit/skills/lifi/schema.json +17 -8
  213. intentkit/skills/lifi/token_execute.py +150 -60
  214. intentkit/skills/lifi/token_quote.py +8 -10
  215. intentkit/skills/lifi/utils.py +104 -51
  216. intentkit/skills/moralis/__init__.py +6 -10
  217. intentkit/skills/moralis/api.py +6 -7
  218. intentkit/skills/moralis/base.py +5 -10
  219. intentkit/skills/moralis/fetch_chain_portfolio.py +10 -11
  220. intentkit/skills/moralis/fetch_nft_portfolio.py +22 -22
  221. intentkit/skills/moralis/fetch_solana_portfolio.py +11 -12
  222. intentkit/skills/moralis/fetch_wallet_portfolio.py +8 -9
  223. intentkit/skills/moralis/schema.json +7 -2
  224. intentkit/skills/morpho/__init__.py +52 -0
  225. intentkit/skills/morpho/base.py +11 -0
  226. intentkit/skills/morpho/morpho.svg +12 -0
  227. intentkit/skills/morpho/schema.json +73 -0
  228. intentkit/skills/nation/__init__.py +4 -9
  229. intentkit/skills/nation/base.py +5 -10
  230. intentkit/skills/nation/nft_check.py +3 -4
  231. intentkit/skills/nation/schema.json +4 -3
  232. intentkit/skills/onchain.py +30 -0
  233. intentkit/skills/openai/__init__.py +17 -18
  234. intentkit/skills/openai/base.py +10 -14
  235. intentkit/skills/openai/dalle_image_generation.py +4 -9
  236. intentkit/skills/openai/gpt_avatar_generator.py +102 -0
  237. intentkit/skills/openai/gpt_image_generation.py +5 -9
  238. intentkit/skills/openai/gpt_image_mini_generator.py +92 -0
  239. intentkit/skills/openai/gpt_image_to_image.py +5 -9
  240. intentkit/skills/openai/image_to_text.py +3 -7
  241. intentkit/skills/openai/schema.json +34 -3
  242. intentkit/skills/portfolio/__init__.py +11 -35
  243. intentkit/skills/portfolio/base.py +33 -19
  244. intentkit/skills/portfolio/schema.json +3 -5
  245. intentkit/skills/portfolio/token_balances.py +21 -21
  246. intentkit/skills/portfolio/wallet_approvals.py +17 -18
  247. intentkit/skills/portfolio/wallet_defi_positions.py +3 -3
  248. intentkit/skills/portfolio/wallet_history.py +31 -31
  249. intentkit/skills/portfolio/wallet_net_worth.py +13 -13
  250. intentkit/skills/portfolio/wallet_nfts.py +19 -19
  251. intentkit/skills/portfolio/wallet_profitability.py +18 -18
  252. intentkit/skills/portfolio/wallet_profitability_summary.py +5 -5
  253. intentkit/skills/portfolio/wallet_stats.py +3 -3
  254. intentkit/skills/portfolio/wallet_swaps.py +19 -19
  255. intentkit/skills/pyth/__init__.py +50 -0
  256. intentkit/skills/pyth/base.py +11 -0
  257. intentkit/skills/pyth/pyth.svg +6 -0
  258. intentkit/skills/pyth/schema.json +75 -0
  259. intentkit/skills/skills.toml +36 -0
  260. intentkit/skills/slack/__init__.py +5 -17
  261. intentkit/skills/slack/base.py +3 -9
  262. intentkit/skills/slack/get_channel.py +8 -8
  263. intentkit/skills/slack/get_message.py +9 -9
  264. intentkit/skills/slack/schedule_message.py +5 -5
  265. intentkit/skills/slack/schema.json +2 -2
  266. intentkit/skills/slack/send_message.py +3 -5
  267. intentkit/skills/supabase/__init__.py +7 -23
  268. intentkit/skills/supabase/base.py +1 -7
  269. intentkit/skills/supabase/delete_data.py +4 -4
  270. intentkit/skills/supabase/fetch_data.py +12 -12
  271. intentkit/skills/supabase/insert_data.py +4 -4
  272. intentkit/skills/supabase/invoke_function.py +6 -6
  273. intentkit/skills/supabase/schema.json +2 -3
  274. intentkit/skills/supabase/update_data.py +6 -6
  275. intentkit/skills/supabase/upsert_data.py +4 -4
  276. intentkit/skills/superfluid/__init__.py +53 -0
  277. intentkit/skills/superfluid/base.py +11 -0
  278. intentkit/skills/superfluid/schema.json +89 -0
  279. intentkit/skills/superfluid/superfluid.svg +6 -0
  280. intentkit/skills/system/__init__.py +7 -24
  281. intentkit/skills/system/add_autonomous_task.py +10 -12
  282. intentkit/skills/system/delete_autonomous_task.py +2 -2
  283. intentkit/skills/system/edit_autonomous_task.py +14 -18
  284. intentkit/skills/system/list_autonomous_tasks.py +3 -5
  285. intentkit/skills/system/read_agent_api_key.py +6 -4
  286. intentkit/skills/system/regenerate_agent_api_key.py +6 -4
  287. intentkit/skills/system/schema.json +6 -8
  288. intentkit/skills/tavily/__init__.py +3 -12
  289. intentkit/skills/tavily/base.py +4 -9
  290. intentkit/skills/tavily/schema.json +3 -5
  291. intentkit/skills/tavily/tavily_extract.py +2 -4
  292. intentkit/skills/tavily/tavily_search.py +4 -6
  293. intentkit/skills/token/__init__.py +5 -10
  294. intentkit/skills/token/base.py +7 -11
  295. intentkit/skills/token/erc20_transfers.py +19 -19
  296. intentkit/skills/token/schema.json +3 -6
  297. intentkit/skills/token/token_analytics.py +3 -3
  298. intentkit/skills/token/token_price.py +13 -13
  299. intentkit/skills/token/token_search.py +9 -9
  300. intentkit/skills/twitter/__init__.py +11 -35
  301. intentkit/skills/twitter/base.py +22 -34
  302. intentkit/skills/twitter/follow_user.py +2 -6
  303. intentkit/skills/twitter/get_mentions.py +5 -12
  304. intentkit/skills/twitter/get_timeline.py +4 -12
  305. intentkit/skills/twitter/get_user_by_username.py +2 -6
  306. intentkit/skills/twitter/get_user_tweets.py +5 -13
  307. intentkit/skills/twitter/like_tweet.py +2 -6
  308. intentkit/skills/twitter/post_tweet.py +6 -9
  309. intentkit/skills/twitter/reply_tweet.py +6 -9
  310. intentkit/skills/twitter/retweet.py +2 -6
  311. intentkit/skills/twitter/schema.json +1 -0
  312. intentkit/skills/twitter/search_tweets.py +4 -12
  313. intentkit/skills/unrealspeech/__init__.py +2 -7
  314. intentkit/skills/unrealspeech/base.py +2 -8
  315. intentkit/skills/unrealspeech/schema.json +2 -5
  316. intentkit/skills/unrealspeech/text_to_speech.py +8 -8
  317. intentkit/skills/venice_audio/__init__.py +98 -106
  318. intentkit/skills/venice_audio/base.py +117 -121
  319. intentkit/skills/venice_audio/input.py +41 -41
  320. intentkit/skills/venice_audio/schema.json +151 -152
  321. intentkit/skills/venice_audio/venice_audio.py +38 -21
  322. intentkit/skills/venice_image/__init__.py +147 -154
  323. intentkit/skills/venice_image/api.py +138 -138
  324. intentkit/skills/venice_image/base.py +185 -192
  325. intentkit/skills/venice_image/config.py +33 -35
  326. intentkit/skills/venice_image/image_enhance/image_enhance.py +2 -3
  327. intentkit/skills/venice_image/image_enhance/image_enhance_base.py +21 -23
  328. intentkit/skills/venice_image/image_enhance/image_enhance_input.py +38 -40
  329. intentkit/skills/venice_image/image_generation/image_generation_base.py +11 -10
  330. intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -26
  331. intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -27
  332. intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -26
  333. intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -158
  334. intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -26
  335. intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -26
  336. intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -28
  337. intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -28
  338. intentkit/skills/venice_image/image_upscale/image_upscale.py +3 -3
  339. intentkit/skills/venice_image/image_upscale/image_upscale_base.py +21 -23
  340. intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -22
  341. intentkit/skills/venice_image/image_vision/image_vision.py +2 -2
  342. intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -17
  343. intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -9
  344. intentkit/skills/venice_image/schema.json +267 -267
  345. intentkit/skills/venice_image/utils.py +77 -78
  346. intentkit/skills/web_scraper/__init__.py +5 -18
  347. intentkit/skills/web_scraper/base.py +21 -7
  348. intentkit/skills/web_scraper/document_indexer.py +7 -6
  349. intentkit/skills/web_scraper/schema.json +2 -6
  350. intentkit/skills/web_scraper/scrape_and_index.py +15 -15
  351. intentkit/skills/web_scraper/utils.py +62 -63
  352. intentkit/skills/web_scraper/website_indexer.py +17 -19
  353. intentkit/skills/weth/__init__.py +49 -0
  354. intentkit/skills/weth/base.py +11 -0
  355. intentkit/skills/weth/schema.json +58 -0
  356. intentkit/skills/weth/weth.svg +6 -0
  357. intentkit/skills/wow/__init__.py +51 -0
  358. intentkit/skills/wow/base.py +11 -0
  359. intentkit/skills/wow/schema.json +89 -0
  360. intentkit/skills/wow/wow.svg +7 -0
  361. intentkit/skills/x402/__init__.py +58 -0
  362. intentkit/skills/x402/base.py +99 -0
  363. intentkit/skills/x402/http_request.py +117 -0
  364. intentkit/skills/x402/schema.json +40 -0
  365. intentkit/skills/x402/x402.webp +0 -0
  366. intentkit/skills/xmtp/__init__.py +4 -15
  367. intentkit/skills/xmtp/base.py +5 -5
  368. intentkit/skills/xmtp/price.py +7 -6
  369. intentkit/skills/xmtp/schema.json +69 -71
  370. intentkit/skills/xmtp/swap.py +6 -8
  371. intentkit/skills/xmtp/transfer.py +4 -6
  372. intentkit/utils/__init__.py +4 -0
  373. intentkit/utils/chain.py +198 -96
  374. intentkit/utils/ens.py +135 -0
  375. intentkit/utils/error.py +5 -2
  376. intentkit/utils/logging.py +9 -11
  377. intentkit/utils/schema.py +100 -0
  378. intentkit/utils/slack_alert.py +8 -8
  379. intentkit/utils/tx.py +16 -8
  380. intentkit/uv.lock +3377 -0
  381. {intentkit-0.7.5.dev3.dist-info → intentkit-0.8.34.dev7.dist-info}/METADATA +13 -15
  382. intentkit-0.8.34.dev7.dist-info/RECORD +478 -0
  383. intentkit-0.8.34.dev7.dist-info/licenses/LICENSE +21 -0
  384. intentkit/core/node.py +0 -215
  385. intentkit/models/conversation.py +0 -286
  386. intentkit/models/generator.py +0 -347
  387. intentkit/skills/cdp/get_balance.py +0 -110
  388. intentkit/skills/cdp/swap.py +0 -121
  389. intentkit/skills/moralis/tests/__init__.py +0 -0
  390. intentkit/skills/moralis/tests/test_wallet.py +0 -511
  391. intentkit-0.7.5.dev3.dist-info/RECORD +0 -424
  392. {intentkit-0.7.5.dev3.dist-info/licenses → intentkit}/LICENSE +0 -0
  393. {intentkit-0.7.5.dev3.dist-info → intentkit-0.8.34.dev7.dist-info}/WHEEL +0 -0
intentkit/core/agent.py CHANGED
@@ -1,137 +1,92 @@
1
1
  import logging
2
2
  import time
3
- from datetime import datetime, timedelta, timezone
3
+ from collections.abc import AsyncGenerator
4
+ from datetime import UTC, datetime, timedelta
4
5
  from decimal import Decimal
5
- from typing import Any, Dict, List, Optional
6
6
 
7
- from aiogram import Bot
8
- from aiogram.exceptions import TelegramConflictError, TelegramUnauthorizedError
9
- from aiogram.utils.token import TokenValidationError
10
7
  from sqlalchemy import func, select, text, update
11
8
 
12
- from intentkit.abstracts.skill import SkillStoreABC
13
- from intentkit.clients.cdp import get_cdp_client
9
+ from intentkit.clients.cdp import get_wallet_provider
14
10
  from intentkit.config.config import config
15
11
  from intentkit.models.agent import (
16
12
  Agent,
17
- AgentAutonomous,
18
13
  AgentCreate,
19
- AgentResponse,
20
14
  AgentTable,
21
15
  AgentUpdate,
22
16
  )
23
- from intentkit.models.agent_data import AgentData, AgentQuota, AgentQuotaTable
24
- from intentkit.models.credit import CreditEventTable, EventType, UpstreamType
25
- from intentkit.models.db import get_session
26
- from intentkit.models.skill import (
27
- AgentSkillData,
28
- AgentSkillDataCreate,
29
- ThreadSkillData,
30
- ThreadSkillDataCreate,
17
+ from intentkit.models.agent_data import AgentData, AgentQuotaTable
18
+ from intentkit.models.credit import (
19
+ CreditAccount,
20
+ CreditEventTable,
21
+ EventType,
22
+ OwnerType,
23
+ UpstreamType,
31
24
  )
25
+ from intentkit.models.db import get_session
32
26
  from intentkit.utils.error import IntentKitAPIError
33
27
  from intentkit.utils.slack_alert import send_slack_message
34
28
 
35
29
  logger = logging.getLogger(__name__)
36
30
 
37
31
 
38
- async def _process_agent_post_actions(
39
- agent: Agent, is_new: bool = True, slack_message: str | None = None
32
+ async def process_agent_wallet(
33
+ agent: Agent, old_wallet_provider: str | None = None
40
34
  ) -> AgentData:
41
- """Process common actions after agent creation or update.
35
+ """Process agent wallet initialization and validation.
42
36
 
43
37
  Args:
44
38
  agent: The agent that was created or updated
45
- is_new: Whether the agent is newly created
46
- slack_message: Optional custom message for Slack notification
39
+ old_wallet_provider: Previous wallet provider (None, "cdp", or "readonly")
47
40
 
48
41
  Returns:
49
42
  AgentData: The processed agent data
43
+
44
+ Raises:
45
+ IntentKitAPIError: If attempting to change between cdp and readonly providers
50
46
  """
51
- if config.cdp_api_key_id and agent.wallet_provider == "cdp":
52
- cdp_client = await get_cdp_client(agent.id, agent_store)
53
- await cdp_client.get_wallet_provider()
47
+ current_wallet_provider = agent.wallet_provider
48
+
49
+ # 1. Check if changing between cdp and readonly (not allowed)
50
+ if (
51
+ old_wallet_provider is not None
52
+ and old_wallet_provider != "none"
53
+ and old_wallet_provider != current_wallet_provider
54
+ ):
55
+ raise IntentKitAPIError(
56
+ 400,
57
+ "WalletProviderChangeNotAllowed",
58
+ "Cannot change wallet provider between cdp and readonly",
59
+ )
54
60
 
55
- # Get new agent data
56
- # FIXME: refuse to change wallet provider
57
- if agent.wallet_provider == "readonly":
61
+ # 2. If wallet provider hasn't changed, return existing agent data
62
+ if (
63
+ old_wallet_provider is not None
64
+ and old_wallet_provider != "none"
65
+ and old_wallet_provider == current_wallet_provider
66
+ ):
67
+ return await AgentData.get(agent.id)
68
+
69
+ # 3. For new agents (old_wallet_provider is None), check if wallet already exists
70
+ agent_data = await AgentData.get(agent.id)
71
+ if agent_data.evm_wallet_address:
72
+ return agent_data
73
+
74
+ # 4. Initialize wallet based on provider type
75
+ if config.cdp_api_key_id and current_wallet_provider == "cdp":
76
+ await get_wallet_provider(agent)
77
+ agent_data = await AgentData.get(agent.id)
78
+ elif current_wallet_provider == "readonly":
58
79
  agent_data = await AgentData.patch(
59
80
  agent.id,
60
81
  {
61
82
  "evm_wallet_address": agent.readonly_wallet_address,
62
83
  },
63
84
  )
64
- else:
65
- agent_data = await AgentData.get(agent.id)
66
-
67
- # Send Slack notification
68
- slack_message = slack_message or ("Agent Created" if is_new else "Agent Updated")
69
- try:
70
- _send_agent_notification(agent, agent_data, slack_message)
71
- except Exception as e:
72
- logger.error("Failed to send Slack notification: %s", e)
73
85
 
74
86
  return agent_data
75
87
 
76
88
 
77
- async def _process_telegram_config(
78
- agent: AgentUpdate, existing_agent: Optional[Agent], agent_data: AgentData
79
- ) -> AgentData:
80
- """Process telegram configuration for an agent.
81
-
82
- Args:
83
- agent: The agent with telegram configuration
84
- existing_agent: The existing agent (if updating)
85
- agent_data: The agent data to update
86
-
87
- Returns:
88
- AgentData: The updated agent data
89
- """
90
- changes = agent.model_dump(exclude_unset=True)
91
- if not changes.get("telegram_entrypoint_enabled"):
92
- return agent_data
93
-
94
- if not changes.get("telegram_config") or not changes.get("telegram_config").get(
95
- "token"
96
- ):
97
- return agent_data
98
-
99
- tg_bot_token = changes.get("telegram_config").get("token")
100
-
101
- if existing_agent and existing_agent.telegram_config.get("token") == tg_bot_token:
102
- return agent_data
103
-
104
- try:
105
- bot = Bot(token=tg_bot_token)
106
- bot_info = await bot.get_me()
107
- agent_data.telegram_id = str(bot_info.id)
108
- agent_data.telegram_username = bot_info.username
109
- agent_data.telegram_name = bot_info.first_name
110
- if bot_info.last_name:
111
- agent_data.telegram_name = f"{bot_info.first_name} {bot_info.last_name}"
112
- await agent_data.save()
113
- try:
114
- await bot.close()
115
- except Exception:
116
- pass
117
- return agent_data
118
- except (
119
- TelegramUnauthorizedError,
120
- TelegramConflictError,
121
- TokenValidationError,
122
- ) as req_err:
123
- logger.error(
124
- f"Unauthorized err getting telegram bot username with token {tg_bot_token}: {req_err}",
125
- )
126
- return agent_data
127
- except Exception as e:
128
- logger.error(
129
- f"Error getting telegram bot username with token {tg_bot_token}: {e}",
130
- )
131
- return agent_data
132
-
133
-
134
- def _send_agent_notification(agent: Agent, agent_data: AgentData, message: str) -> None:
89
+ def send_agent_notification(agent: Agent, agent_data: AgentData, message: str) -> None:
135
90
  """Send a notification about agent creation or update.
136
91
 
137
92
  Args:
@@ -202,7 +157,7 @@ def _send_agent_notification(agent: Agent, agent_data: AgentData, message: str)
202
157
  {
203
158
  "title": "Network",
204
159
  "short": True,
205
- "value": agent.network_id or "Default",
160
+ "value": agent.network_id or "Not Set",
206
161
  },
207
162
  {
208
163
  "title": "X Username",
@@ -237,71 +192,122 @@ def _send_agent_notification(agent: Agent, agent_data: AgentData, message: str)
237
192
  )
238
193
 
239
194
 
240
- async def deploy_agent(
241
- agent_id: str, agent: AgentUpdate, owner: Optional[str] = None
242
- ) -> AgentResponse:
243
- """Override an existing agent.
195
+ async def override_agent(
196
+ agent_id: str, agent: AgentUpdate, owner: str | None = None
197
+ ) -> tuple[Agent, AgentData]:
198
+ """Override an existing agent with new configuration.
244
199
 
245
- Use input to override agent configuration. If some fields are not provided, they will be reset to default values.
200
+ This function updates an existing agent with the provided configuration.
201
+ If some fields are not provided, they will be reset to default values.
246
202
 
247
203
  Args:
248
- agent_id: ID of the agent to update
249
- agent: Agent update configuration
250
- owner: Optional owner for the agent
204
+ agent_id: ID of the agent to override
205
+ agent: Agent update configuration containing the new settings
206
+ owner: Optional owner for permission validation
251
207
 
252
208
  Returns:
253
- AgentResponse: Updated agent configuration with additional processed data
209
+ tuple[Agent, AgentData]: Updated agent configuration and processed agent data
254
210
 
255
211
  Raises:
256
- HTTPException:
257
- - 400: Invalid agent ID format
212
+ IntentKitAPIError:
258
213
  - 404: Agent not found
259
214
  - 403: Permission denied (if owner mismatch)
260
- - 500: Database error
215
+ - 400: Invalid configuration or wallet provider change
261
216
  """
262
217
  existing_agent = await Agent.get(agent_id)
263
218
  if not existing_agent:
264
- agent = AgentCreate.model_validate(input)
265
- agent.id = agent_id
266
- if owner:
267
- agent.owner = owner
268
- else:
269
- agent.owner = "system"
270
- # Check for existing agent by upstream_id, forward compatibility, raise error after 3.0
271
- existing = await agent.get_by_upstream_id()
272
- if existing:
273
- agent_data = await AgentData.get(existing.id)
274
- agent_response = await AgentResponse.from_agent(existing, agent_data)
275
- return agent_response
276
-
277
- # Create new agent
278
- latest_agent = await agent.create()
279
- # Process common post-creation actions
280
- agent_data = await _process_agent_post_actions(
281
- latest_agent, True, "Agent Created"
219
+ raise IntentKitAPIError(
220
+ status_code=404,
221
+ key="AgentNotFound",
222
+ message=f"Agent with ID '{agent_id}' not found",
282
223
  )
283
- agent_data = await _process_telegram_config(input, None, agent_data)
284
- agent_response = await AgentResponse.from_agent(latest_agent, agent_data)
285
-
286
- return agent_response
287
-
288
224
  if owner and owner != existing_agent.owner:
289
- raise IntentKitAPIError(403, "forbidden", "forbidden")
225
+ raise IntentKitAPIError(403, "Forbidden", "forbidden")
290
226
 
291
227
  # Update agent
292
228
  latest_agent = await agent.override(agent_id)
293
-
294
- # Process common post-update actions
295
- agent_data = await _process_agent_post_actions(
296
- latest_agent, False, "Agent Overridden"
229
+ agent_data = await process_agent_wallet(
230
+ latest_agent, existing_agent.wallet_provider
297
231
  )
298
- agent_data = await _process_telegram_config(agent, existing_agent, agent_data)
299
- agent_response = await AgentResponse.from_agent(latest_agent, agent_data)
232
+ send_agent_notification(latest_agent, agent_data, "Agent Overridden Deployed")
233
+
234
+ return latest_agent, agent_data
235
+
300
236
 
301
- return agent_response
237
+ async def create_agent(agent: AgentCreate) -> tuple[Agent, AgentData]:
238
+ """Create a new agent with the provided configuration.
302
239
 
240
+ This function creates a new agent instance with the given configuration,
241
+ initializes its wallet, and sends a notification about the creation.
303
242
 
304
- async def agent_action_cost(agent_id: str) -> Dict[str, Decimal]:
243
+ Args:
244
+ agent: Agent creation configuration containing all necessary settings
245
+
246
+ Returns:
247
+ tuple[Agent, AgentData]: Created agent configuration and processed agent data
248
+
249
+ Raises:
250
+ IntentKitAPIError:
251
+ - 400: Agent with upstream ID already exists or invalid configuration
252
+ - 500: Database error or wallet initialization failure
253
+ """
254
+ if not agent.owner:
255
+ agent.owner = "system"
256
+ # Check for existing agent by upstream_id, forward compatibility, raise error after 3.0
257
+ existing = await agent.get_by_upstream_id()
258
+ if existing:
259
+ raise IntentKitAPIError(
260
+ status_code=400,
261
+ key="BadRequest",
262
+ message="Agent with this upstream ID already exists",
263
+ )
264
+
265
+ # Create new agent
266
+ latest_agent = await agent.create()
267
+ agent_data = await process_agent_wallet(latest_agent)
268
+ send_agent_notification(latest_agent, agent_data, "Agent Deployed")
269
+
270
+ return latest_agent, agent_data
271
+
272
+
273
+ async def deploy_agent(
274
+ agent_id: str, agent: AgentUpdate, owner: str | None = None
275
+ ) -> tuple[Agent, AgentData]:
276
+ """Deploy an agent by first attempting to override, then creating if not found.
277
+
278
+ This function first tries to override an existing agent. If the agent is not found
279
+ (404 error), it will create a new agent instead.
280
+
281
+ Args:
282
+ agent_id: ID of the agent to deploy
283
+ agent: Agent configuration data
284
+ owner: Optional owner for the agent
285
+
286
+ Returns:
287
+ tuple[Agent, AgentData]: Deployed agent configuration and processed agent data
288
+
289
+ Raises:
290
+ IntentKitAPIError:
291
+ - 400: Invalid agent configuration or upstream ID conflict
292
+ - 403: Permission denied (if owner mismatch)
293
+ - 500: Database error
294
+ """
295
+ try:
296
+ # First try to override the existing agent
297
+ return await override_agent(agent_id, agent, owner)
298
+ except IntentKitAPIError as e:
299
+ # If agent not found (404), create a new one
300
+ if e.status_code == 404:
301
+ new_agent = AgentCreate.model_validate(agent)
302
+ new_agent.id = agent_id
303
+ new_agent.owner = owner
304
+ return await create_agent(new_agent)
305
+ else:
306
+ # Re-raise other errors
307
+ raise
308
+
309
+
310
+ async def agent_action_cost(agent_id: str) -> dict[str, Decimal]:
305
311
  """
306
312
  Calculate various action cost metrics for an agent based on past three days of credit events.
307
313
 
@@ -317,7 +323,7 @@ async def agent_action_cost(agent_id: str) -> Dict[str, Decimal]:
317
323
  agent_id: ID of the agent
318
324
 
319
325
  Returns:
320
- Dict[str, Decimal]: Dictionary containing all calculated cost metrics
326
+ dict[str, Decimal]: Dictionary containing all calculated cost metrics
321
327
  """
322
328
  start_time = time.time()
323
329
  default_value = Decimal("0")
@@ -330,7 +336,7 @@ async def agent_action_cost(agent_id: str) -> Dict[str, Decimal]:
330
336
 
331
337
  async with get_session() as session:
332
338
  # Calculate the date 3 days ago from now
333
- three_days_ago = datetime.now(timezone.utc) - timedelta(days=3)
339
+ three_days_ago = datetime.now(UTC) - timedelta(days=3)
334
340
 
335
341
  # First, count the number of distinct start_message_ids to determine if we have enough data
336
342
  count_query = select(
@@ -478,183 +484,31 @@ async def agent_action_cost(agent_id: str) -> Dict[str, Decimal]:
478
484
  return result
479
485
 
480
486
 
481
- class AgentStore(SkillStoreABC):
482
- """Implementation of skill data storage operations.
483
-
484
- This class provides concrete implementations for storing and retrieving
485
- skill-related data for both agents and threads.
486
- """
487
-
488
- @staticmethod
489
- def get_system_config(key: str) -> Any:
490
- # TODO: maybe need a whitelist here
491
- if hasattr(config, key):
492
- return getattr(config, key)
493
- return None
494
-
495
- @staticmethod
496
- async def get_agent_config(agent_id: str) -> Optional[Agent]:
497
- return await Agent.get(agent_id)
498
-
499
- @staticmethod
500
- async def get_agent_data(agent_id: str) -> AgentData:
501
- return await AgentData.get(agent_id)
502
-
503
- @staticmethod
504
- async def set_agent_data(agent_id: str, data: Dict) -> AgentData:
505
- return await AgentData.patch(agent_id, data)
506
-
507
- @staticmethod
508
- async def get_agent_quota(agent_id: str) -> AgentQuota:
509
- return await AgentQuota.get(agent_id)
510
-
511
- @staticmethod
512
- async def get_agent_skill_data(
513
- agent_id: str, skill: str, key: str
514
- ) -> Optional[Dict[str, Any]]:
515
- """Get skill data for an agent.
516
-
517
- Args:
518
- agent_id: ID of the agent
519
- skill: Name of the skill
520
- key: Data key
521
-
522
- Returns:
523
- Dictionary containing the skill data if found, None otherwise
524
- """
525
- return await AgentSkillData.get(agent_id, skill, key)
526
-
527
- @staticmethod
528
- async def save_agent_skill_data(
529
- agent_id: str, skill: str, key: str, data: Dict[str, Any]
530
- ) -> None:
531
- """Save or update skill data for an agent.
532
-
533
- Args:
534
- agent_id: ID of the agent
535
- skill: Name of the skill
536
- key: Data key
537
- data: JSON data to store
538
- """
539
- skill_data = AgentSkillDataCreate(
540
- agent_id=agent_id,
541
- skill=skill,
542
- key=key,
543
- data=data,
544
- )
545
- await skill_data.save()
546
-
547
- @staticmethod
548
- async def delete_agent_skill_data(agent_id: str, skill: str, key: str) -> None:
549
- """Delete skill data for an agent.
550
-
551
- Args:
552
- agent_id: ID of the agent
553
- skill: Name of the skill
554
- key: Data key
555
- """
556
- await AgentSkillData.delete(agent_id, skill, key)
557
-
558
- @staticmethod
559
- async def get_thread_skill_data(
560
- thread_id: str, skill: str, key: str
561
- ) -> Optional[Dict[str, Any]]:
562
- """Get skill data for a thread.
563
-
564
- Args:
565
- thread_id: ID of the thread
566
- skill: Name of the skill
567
- key: Data key
568
-
569
- Returns:
570
- Dictionary containing the skill data if found, None otherwise
571
- """
572
- return await ThreadSkillData.get(thread_id, skill, key)
573
-
574
- @staticmethod
575
- async def save_thread_skill_data(
576
- thread_id: str,
577
- agent_id: str,
578
- skill: str,
579
- key: str,
580
- data: Dict[str, Any],
581
- ) -> None:
582
- """Save or update skill data for a thread.
583
-
584
- Args:
585
- thread_id: ID of the thread
586
- agent_id: ID of the agent that owns this thread
587
- skill: Name of the skill
588
- key: Data key
589
- data: JSON data to store
590
- """
591
- skill_data = ThreadSkillDataCreate(
592
- thread_id=thread_id,
593
- agent_id=agent_id,
594
- skill=skill,
595
- key=key,
596
- data=data,
597
- )
598
- await skill_data.save()
599
-
600
- @staticmethod
601
- async def list_autonomous_tasks(agent_id: str) -> List[AgentAutonomous]:
602
- """List all autonomous tasks for an agent.
603
-
604
- Args:
605
- agent_id: ID of the agent
487
+ async def _iterate_agent_id_batches(
488
+ batch_size: int = 100,
489
+ ) -> AsyncGenerator[list[str], None]:
490
+ """Yield agent IDs in ascending batches to limit memory usage."""
606
491
 
607
- Returns:
608
- List[AgentAutonomous]: List of autonomous task configurations
609
- """
610
- return await list_autonomous_tasks(agent_id)
611
-
612
- @staticmethod
613
- async def add_autonomous_task(
614
- agent_id: str, task: AgentAutonomous
615
- ) -> AgentAutonomous:
616
- """Add a new autonomous task to an agent.
617
-
618
- Args:
619
- agent_id: ID of the agent
620
- task: Autonomous task configuration
621
-
622
- Returns:
623
- AgentAutonomous: The created task
624
- """
625
- return await add_autonomous_task(agent_id, task)
626
-
627
- @staticmethod
628
- async def delete_autonomous_task(agent_id: str, task_id: str) -> None:
629
- """Delete an autonomous task from an agent.
630
-
631
- Args:
632
- agent_id: ID of the agent
633
- task_id: ID of the task to delete
634
- """
635
- await delete_autonomous_task(agent_id, task_id)
636
-
637
- @staticmethod
638
- async def update_autonomous_task(
639
- agent_id: str, task_id: str, task_updates: dict
640
- ) -> AgentAutonomous:
641
- """Update an autonomous task for an agent.
492
+ last_id: str | None = None
493
+ while True:
494
+ async with get_session() as session:
495
+ query = select(AgentTable.id).order_by(AgentTable.id)
642
496
 
643
- Args:
644
- agent_id: ID of the agent
645
- task_id: ID of the task to update
646
- task_updates: Dictionary containing fields to update
497
+ if last_id:
498
+ query = query.where(AgentTable.id > last_id)
647
499
 
648
- Returns:
649
- AgentAutonomous: The updated task
650
- """
651
- return await update_autonomous_task(agent_id, task_id, task_updates)
500
+ query = query.limit(batch_size)
501
+ result = await session.execute(query)
502
+ agent_ids = [row[0] for row in result]
652
503
 
504
+ if not agent_ids:
505
+ break
653
506
 
654
- agent_store = AgentStore()
507
+ yield agent_ids
508
+ last_id = agent_ids[-1]
655
509
 
656
510
 
657
- async def update_agent_action_cost():
511
+ async def update_agent_action_cost(batch_size: int = 100) -> None:
658
512
  """
659
513
  Update action costs for all agents.
660
514
 
@@ -671,42 +525,20 @@ async def update_agent_action_cost():
671
525
  """
672
526
  logger.info("Starting update of agent average action costs")
673
527
  start_time = time.time()
674
- batch_size = 100
675
- last_id = None
676
528
  total_updated = 0
677
529
 
678
- while True:
679
- # Get a batch of agent IDs ordered by ID
680
- async with get_session() as session:
681
- query = select(AgentTable.id).order_by(AgentTable.id)
682
-
683
- # Apply pagination if we have a last_id from previous batch
684
- if last_id:
685
- query = query.where(AgentTable.id > last_id)
686
-
687
- query = query.limit(batch_size)
688
- result = await session.execute(query)
689
- agent_ids = [row[0] for row in result]
690
-
691
- # If no more agents, we're done
692
- if not agent_ids:
693
- break
694
-
695
- # Update last_id for next batch
696
- last_id = agent_ids[-1]
697
-
698
- # Process this batch of agents
530
+ async for agent_ids in _iterate_agent_id_batches(batch_size):
699
531
  logger.info(
700
- f"Processing batch of {len(agent_ids)} agents starting with ID {agent_ids[0]}"
532
+ "Processing batch of %s agents starting with ID %s",
533
+ len(agent_ids),
534
+ agent_ids[0],
701
535
  )
702
536
  batch_start_time = time.time()
703
537
 
704
538
  for agent_id in agent_ids:
705
539
  try:
706
- # Calculate action costs for this agent
707
540
  costs = await agent_action_cost(agent_id)
708
541
 
709
- # Update the agent's quota record
710
542
  async with get_session() as session:
711
543
  update_stmt = (
712
544
  update(AgentQuotaTable)
@@ -724,203 +556,186 @@ async def update_agent_action_cost():
724
556
  await session.commit()
725
557
 
726
558
  total_updated += 1
727
- except Exception as e:
559
+ except Exception as e: # pragma: no cover - log path only
728
560
  logger.error(
729
- f"Error updating action costs for agent {agent_id}: {str(e)}"
561
+ "Error updating action costs for agent %s: %s", agent_id, str(e)
730
562
  )
731
563
 
732
564
  batch_time = time.time() - batch_start_time
733
- logger.info(f"Completed batch in {batch_time:.3f}s")
565
+ logger.info("Completed batch in %.3fs", batch_time)
734
566
 
735
567
  total_time = time.time() - start_time
736
568
  logger.info(
737
- f"Finished updating action costs for {total_updated} agents in {total_time:.3f}s"
569
+ "Finished updating action costs for %s agents in %.3fs",
570
+ total_updated,
571
+ total_time,
738
572
  )
739
573
 
740
574
 
741
- async def list_autonomous_tasks(agent_id: str) -> List[AgentAutonomous]:
742
- """
743
- List all autonomous tasks for an agent.
744
-
745
- Args:
746
- agent_id: ID of the agent
575
+ async def update_agents_account_snapshot(batch_size: int = 100) -> None:
576
+ """Refresh the cached credit account snapshot for every agent."""
747
577
 
748
- Returns:
749
- List[AgentAutonomous]: List of autonomous task configurations
578
+ logger.info("Starting update of agent account snapshots")
579
+ start_time = time.time()
580
+ total_updated = 0
750
581
 
751
- Raises:
752
- IntentKitAPIError: If agent is not found
753
- """
754
- agent = await Agent.get(agent_id)
755
- if not agent:
756
- raise IntentKitAPIError(
757
- 400, "AgentNotFound", f"Agent with ID {agent_id} does not exist."
582
+ async for agent_ids in _iterate_agent_id_batches(batch_size):
583
+ logger.info(
584
+ "Processing snapshot batch of %s agents starting with ID %s",
585
+ len(agent_ids),
586
+ agent_ids[0],
758
587
  )
588
+ batch_start_time = time.time()
759
589
 
760
- if not agent.autonomous:
761
- return []
762
-
763
- return agent.autonomous
764
-
590
+ for agent_id in agent_ids:
591
+ try:
592
+ async with get_session() as session:
593
+ account = await CreditAccount.get_or_create_in_session(
594
+ session, OwnerType.AGENT, agent_id
595
+ )
596
+ await session.execute(
597
+ update(AgentTable)
598
+ .where(AgentTable.id == agent_id)
599
+ .values(
600
+ account_snapshot=account.model_dump(mode="json"),
601
+ )
602
+ )
603
+ await session.commit()
765
604
 
766
- async def add_autonomous_task(agent_id: str, task: AgentAutonomous) -> AgentAutonomous:
767
- """
768
- Add a new autonomous task to an agent.
605
+ total_updated += 1
606
+ except Exception as exc: # pragma: no cover - log path only
607
+ logger.error(
608
+ "Error updating account snapshot for agent %s: %s",
609
+ agent_id,
610
+ exc,
611
+ )
769
612
 
770
- Args:
771
- agent_id: ID of the agent
772
- task: Autonomous task configuration (id will be generated if not provided)
613
+ batch_time = time.time() - batch_start_time
614
+ logger.info("Completed snapshot batch in %.3fs", batch_time)
773
615
 
774
- Returns:
775
- AgentAutonomous: The created task with generated ID
616
+ total_time = time.time() - start_time
617
+ logger.info(
618
+ "Finished updating account snapshots for %s agents in %.3fs",
619
+ total_updated,
620
+ total_time,
621
+ )
776
622
 
777
- Raises:
778
- IntentKitAPIError: If agent is not found
779
- """
780
- agent = await Agent.get(agent_id)
781
- if not agent:
782
- raise IntentKitAPIError(
783
- 400, "AgentNotFound", f"Agent with ID {agent_id} does not exist."
784
- )
785
623
 
786
- # Get current autonomous tasks
787
- current_tasks = agent.autonomous or []
788
- if not isinstance(current_tasks, list):
789
- current_tasks = []
624
+ async def update_agents_assets(batch_size: int = 100) -> None:
625
+ """Refresh cached asset information for all agents."""
790
626
 
791
- # Add the new task
792
- current_tasks.append(task)
627
+ from intentkit.core.asset import agent_asset
793
628
 
794
- # Convert all AgentAutonomous objects to dictionaries for JSON serialization
795
- serializable_tasks = [task_item.model_dump() for task_item in current_tasks]
629
+ logger.info("Starting update of agent assets")
630
+ start_time = time.time()
631
+ total_updated = 0
796
632
 
797
- # Update the agent in the database
798
- async with get_session() as session:
799
- update_stmt = (
800
- update(AgentTable)
801
- .where(AgentTable.id == agent_id)
802
- .values(autonomous=serializable_tasks)
633
+ async for agent_ids in _iterate_agent_id_batches(batch_size):
634
+ logger.info(
635
+ "Processing asset batch of %s agents starting with ID %s",
636
+ len(agent_ids),
637
+ agent_ids[0],
803
638
  )
804
- await session.execute(update_stmt)
805
- await session.commit()
806
-
807
- logger.info(f"Added autonomous task {task.id} to agent {agent_id}")
808
- return task
809
-
810
-
811
- async def delete_autonomous_task(agent_id: str, task_id: str) -> None:
812
- """
813
- Delete an autonomous task from an agent.
639
+ batch_start_time = time.time()
814
640
 
815
- Args:
816
- agent_id: ID of the agent
817
- task_id: ID of the task to delete
641
+ for agent_id in agent_ids:
642
+ try:
643
+ assets = await agent_asset(agent_id)
644
+ except IntentKitAPIError as exc: # pragma: no cover - log path only
645
+ logger.warning(
646
+ "Skipping asset update for agent %s due to API error: %s",
647
+ agent_id,
648
+ exc,
649
+ )
650
+ continue
651
+ except Exception as exc: # pragma: no cover - log path only
652
+ logger.error("Error retrieving assets for agent %s: %s", agent_id, exc)
653
+ continue
818
654
 
819
- Raises:
820
- IntentKitAPIError: If agent is not found or task is not found
821
- """
822
- agent = await Agent.get(agent_id)
823
- if not agent:
824
- raise IntentKitAPIError(
825
- 400, "AgentNotFound", f"Agent with ID {agent_id} does not exist."
826
- )
655
+ try:
656
+ async with get_session() as session:
657
+ await session.execute(
658
+ update(AgentTable)
659
+ .where(AgentTable.id == agent_id)
660
+ .values(assets=assets.model_dump(mode="json"))
661
+ )
662
+ await session.commit()
827
663
 
828
- # Get current autonomous tasks
829
- current_tasks = agent.autonomous or []
830
- if not isinstance(current_tasks, list):
831
- current_tasks = []
832
-
833
- # Find and remove the task
834
- task_found = False
835
- updated_tasks = []
836
- for task_data in current_tasks:
837
- if task_data.id == task_id:
838
- task_found = True
839
- continue
840
- updated_tasks.append(task_data)
841
-
842
- if not task_found:
843
- raise IntentKitAPIError(
844
- 404, "TaskNotFound", f"Autonomous task with ID {task_id} not found."
845
- )
664
+ total_updated += 1
665
+ except Exception as exc: # pragma: no cover - log path only
666
+ logger.error(
667
+ "Error updating asset cache for agent %s: %s", agent_id, exc
668
+ )
846
669
 
847
- # Convert remaining AgentAutonomous objects to dictionaries for JSON serialization
848
- serializable_tasks = [task_item.model_dump() for task_item in updated_tasks]
670
+ batch_time = time.time() - batch_start_time
671
+ logger.info("Completed asset batch in %.3fs", batch_time)
849
672
 
850
- # Update the agent in the database
851
- async with get_session() as session:
852
- update_stmt = (
853
- update(AgentTable)
854
- .where(AgentTable.id == agent_id)
855
- .values(autonomous=serializable_tasks)
856
- )
857
- await session.execute(update_stmt)
858
- await session.commit()
673
+ total_time = time.time() - start_time
674
+ logger.info(
675
+ "Finished updating assets for %s agents in %.3fs",
676
+ total_updated,
677
+ total_time,
678
+ )
859
679
 
860
- logger.info(f"Deleted autonomous task {task_id} from agent {agent_id}")
861
680
 
681
+ async def update_agents_statistics(
682
+ *, end_time: datetime | None = None, batch_size: int = 100
683
+ ) -> None:
684
+ """Refresh cached statistics for every agent."""
862
685
 
863
- async def update_autonomous_task(
864
- agent_id: str, task_id: str, task_updates: dict
865
- ) -> AgentAutonomous:
866
- """
867
- Update an autonomous task for an agent.
686
+ from intentkit.core.statistics import get_agent_statistics
868
687
 
869
- Args:
870
- agent_id: ID of the agent
871
- task_id: ID of the task to update
872
- task_updates: Dictionary containing fields to update
688
+ if end_time is None:
689
+ end_time = datetime.now(UTC)
690
+ elif end_time.tzinfo is None:
691
+ end_time = end_time.replace(tzinfo=UTC)
692
+ else:
693
+ end_time = end_time.astimezone(UTC)
873
694
 
874
- Returns:
875
- AgentAutonomous: The updated task
695
+ logger.info("Starting update of agent statistics using end_time %s", end_time)
696
+ start_time = time.time()
697
+ total_updated = 0
876
698
 
877
- Raises:
878
- IntentKitAPIError: If agent is not found or task is not found
879
- """
880
- agent = await Agent.get(agent_id)
881
- if not agent:
882
- raise IntentKitAPIError(
883
- 400, "AgentNotFound", f"Agent with ID {agent_id} does not exist."
699
+ async for agent_ids in _iterate_agent_id_batches(batch_size):
700
+ logger.info(
701
+ "Processing statistics batch of %s agents starting with ID %s",
702
+ len(agent_ids),
703
+ agent_ids[0],
884
704
  )
705
+ batch_start_time = time.time()
885
706
 
886
- # Get current autonomous tasks
887
- current_tasks: List[AgentAutonomous] = agent.autonomous or []
888
-
889
- # Find and update the task
890
- task_found = False
891
- updated_tasks: List[AgentAutonomous] = []
892
- updated_task = None
893
-
894
- for task_data in current_tasks:
895
- if task_data.id == task_id:
896
- task_found = True
897
- # Create a dictionary with current task data
898
- task_dict = task_data.model_dump()
899
- # Update with provided fields
900
- task_dict.update(task_updates)
901
- # Create new AgentAutonomous instance
902
- updated_task = AgentAutonomous.model_validate(task_dict)
903
- updated_tasks.append(updated_task)
904
- else:
905
- updated_tasks.append(task_data)
707
+ for agent_id in agent_ids:
708
+ try:
709
+ statistics = await get_agent_statistics(agent_id, end_time=end_time)
710
+ except Exception as exc: # pragma: no cover - log path only
711
+ logger.error(
712
+ "Error computing statistics for agent %s: %s", agent_id, exc
713
+ )
714
+ continue
906
715
 
907
- if not task_found:
908
- raise IntentKitAPIError(
909
- 404, "TaskNotFound", f"Autonomous task with ID {task_id} not found."
910
- )
716
+ try:
717
+ async with get_session() as session:
718
+ await session.execute(
719
+ update(AgentTable)
720
+ .where(AgentTable.id == agent_id)
721
+ .values(statistics=statistics.model_dump(mode="json"))
722
+ )
723
+ await session.commit()
911
724
 
912
- # Convert all AgentAutonomous objects to dictionaries for JSON serialization
913
- serializable_tasks = [task_item.model_dump() for task_item in updated_tasks]
725
+ total_updated += 1
726
+ except Exception as exc: # pragma: no cover - log path only
727
+ logger.error(
728
+ "Error updating statistics cache for agent %s: %s",
729
+ agent_id,
730
+ exc,
731
+ )
914
732
 
915
- # Update the agent in the database
916
- async with get_session() as session:
917
- update_stmt = (
918
- update(AgentTable)
919
- .where(AgentTable.id == agent_id)
920
- .values(autonomous=serializable_tasks)
921
- )
922
- await session.execute(update_stmt)
923
- await session.commit()
733
+ batch_time = time.time() - batch_start_time
734
+ logger.info("Completed statistics batch in %.3fs", batch_time)
924
735
 
925
- logger.info(f"Updated autonomous task {task_id} for agent {agent_id}")
926
- return updated_task
736
+ total_time = time.time() - start_time
737
+ logger.info(
738
+ "Finished updating statistics for %s agents in %.3fs",
739
+ total_updated,
740
+ total_time,
741
+ )