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
intentkit/core/agent.py CHANGED
@@ -1,21 +1,313 @@
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 Dict, List
6
6
 
7
7
  from sqlalchemy import func, select, text, update
8
8
 
9
- from intentkit.models.agent import Agent, AgentAutonomous, AgentTable
10
- from intentkit.models.agent_data import AgentQuotaTable
11
- from intentkit.models.credit import CreditEventTable, EventType, UpstreamType
9
+ from intentkit.clients.cdp import get_wallet_provider
10
+ from intentkit.config.config import config
11
+ from intentkit.models.agent import (
12
+ Agent,
13
+ AgentCreate,
14
+ AgentTable,
15
+ AgentUpdate,
16
+ )
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,
24
+ )
12
25
  from intentkit.models.db import get_session
13
26
  from intentkit.utils.error import IntentKitAPIError
27
+ from intentkit.utils.slack_alert import send_slack_message
14
28
 
15
29
  logger = logging.getLogger(__name__)
16
30
 
17
31
 
18
- async def agent_action_cost(agent_id: str) -> Dict[str, Decimal]:
32
+ async def process_agent_wallet(
33
+ agent: Agent, old_wallet_provider: str | None = None
34
+ ) -> AgentData:
35
+ """Process agent wallet initialization and validation.
36
+
37
+ Args:
38
+ agent: The agent that was created or updated
39
+ old_wallet_provider: Previous wallet provider (None, "cdp", or "readonly")
40
+
41
+ Returns:
42
+ AgentData: The processed agent data
43
+
44
+ Raises:
45
+ IntentKitAPIError: If attempting to change between cdp and readonly providers
46
+ """
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
+ )
60
+
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":
79
+ agent_data = await AgentData.patch(
80
+ agent.id,
81
+ {
82
+ "evm_wallet_address": agent.readonly_wallet_address,
83
+ },
84
+ )
85
+
86
+ return agent_data
87
+
88
+
89
+ def send_agent_notification(agent: Agent, agent_data: AgentData, message: str) -> None:
90
+ """Send a notification about agent creation or update.
91
+
92
+ Args:
93
+ agent: The agent that was created or updated
94
+ agent_data: The agent data to update
95
+ message: The notification message
96
+ """
97
+ # Format autonomous configurations - show only enabled ones with their id, name, and schedule
98
+ autonomous_formatted = ""
99
+ if agent.autonomous:
100
+ enabled_autonomous = [auto for auto in agent.autonomous if auto.enabled]
101
+ if enabled_autonomous:
102
+ autonomous_items = []
103
+ for auto in enabled_autonomous:
104
+ schedule = (
105
+ f"cron: {auto.cron}" if auto.cron else f"minutes: {auto.minutes}"
106
+ )
107
+ autonomous_items.append(
108
+ f"• {auto.id}: {auto.name or 'Unnamed'} ({schedule})"
109
+ )
110
+ autonomous_formatted = "\n".join(autonomous_items)
111
+ else:
112
+ autonomous_formatted = "No enabled autonomous configurations"
113
+ else:
114
+ autonomous_formatted = "None"
115
+
116
+ # Format skills - find categories with enabled: true and list skills in public/private states
117
+ skills_formatted = ""
118
+ if agent.skills:
119
+ enabled_categories = []
120
+ for category, skill_config in agent.skills.items():
121
+ if skill_config and skill_config.get("enabled") is True:
122
+ skills_list = []
123
+ states = skill_config.get("states", {})
124
+ public_skills = [
125
+ skill for skill, state in states.items() if state == "public"
126
+ ]
127
+ private_skills = [
128
+ skill for skill, state in states.items() if state == "private"
129
+ ]
130
+
131
+ if public_skills:
132
+ skills_list.append(f" Public: {', '.join(public_skills)}")
133
+ if private_skills:
134
+ skills_list.append(f" Private: {', '.join(private_skills)}")
135
+
136
+ if skills_list:
137
+ enabled_categories.append(
138
+ f"• {category}:\n{chr(10).join(skills_list)}"
139
+ )
140
+
141
+ if enabled_categories:
142
+ skills_formatted = "\n".join(enabled_categories)
143
+ else:
144
+ skills_formatted = "No enabled skills"
145
+ else:
146
+ skills_formatted = "None"
147
+
148
+ send_slack_message(
149
+ message,
150
+ attachments=[
151
+ {
152
+ "color": "good",
153
+ "fields": [
154
+ {"title": "ID", "short": True, "value": agent.id},
155
+ {"title": "Name", "short": True, "value": agent.name},
156
+ {"title": "Model", "short": True, "value": agent.model},
157
+ {
158
+ "title": "Network",
159
+ "short": True,
160
+ "value": agent.network_id or "Not Set",
161
+ },
162
+ {
163
+ "title": "X Username",
164
+ "short": True,
165
+ "value": agent_data.twitter_username,
166
+ },
167
+ {
168
+ "title": "Telegram Enabled",
169
+ "short": True,
170
+ "value": str(agent.telegram_entrypoint_enabled),
171
+ },
172
+ {
173
+ "title": "Telegram Username",
174
+ "short": True,
175
+ "value": agent_data.telegram_username,
176
+ },
177
+ {
178
+ "title": "Wallet Address",
179
+ "value": agent_data.evm_wallet_address,
180
+ },
181
+ {
182
+ "title": "Autonomous",
183
+ "value": autonomous_formatted,
184
+ },
185
+ {
186
+ "title": "Skills",
187
+ "value": skills_formatted,
188
+ },
189
+ ],
190
+ }
191
+ ],
192
+ )
193
+
194
+
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.
199
+
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.
202
+
203
+ Args:
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
207
+
208
+ Returns:
209
+ tuple[Agent, AgentData]: Updated agent configuration and processed agent data
210
+
211
+ Raises:
212
+ IntentKitAPIError:
213
+ - 404: Agent not found
214
+ - 403: Permission denied (if owner mismatch)
215
+ - 400: Invalid configuration or wallet provider change
216
+ """
217
+ existing_agent = await Agent.get(agent_id)
218
+ if not existing_agent:
219
+ raise IntentKitAPIError(
220
+ status_code=404,
221
+ key="AgentNotFound",
222
+ message=f"Agent with ID '{agent_id}' not found",
223
+ )
224
+ if owner and owner != existing_agent.owner:
225
+ raise IntentKitAPIError(403, "Forbidden", "forbidden")
226
+
227
+ # Update agent
228
+ latest_agent = await agent.override(agent_id)
229
+ agent_data = await process_agent_wallet(
230
+ latest_agent, existing_agent.wallet_provider
231
+ )
232
+ send_agent_notification(latest_agent, agent_data, "Agent Overridden Deployed")
233
+
234
+ return latest_agent, agent_data
235
+
236
+
237
+ async def create_agent(agent: AgentCreate) -> tuple[Agent, AgentData]:
238
+ """Create a new agent with the provided configuration.
239
+
240
+ This function creates a new agent instance with the given configuration,
241
+ initializes its wallet, and sends a notification about the creation.
242
+
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]:
19
311
  """
20
312
  Calculate various action cost metrics for an agent based on past three days of credit events.
21
313
 
@@ -31,7 +323,7 @@ async def agent_action_cost(agent_id: str) -> Dict[str, Decimal]:
31
323
  agent_id: ID of the agent
32
324
 
33
325
  Returns:
34
- Dict[str, Decimal]: Dictionary containing all calculated cost metrics
326
+ dict[str, Decimal]: Dictionary containing all calculated cost metrics
35
327
  """
36
328
  start_time = time.time()
37
329
  default_value = Decimal("0")
@@ -44,7 +336,7 @@ async def agent_action_cost(agent_id: str) -> Dict[str, Decimal]:
44
336
 
45
337
  async with get_session() as session:
46
338
  # Calculate the date 3 days ago from now
47
- three_days_ago = datetime.now(timezone.utc) - timedelta(days=3)
339
+ three_days_ago = datetime.now(UTC) - timedelta(days=3)
48
340
 
49
341
  # First, count the number of distinct start_message_ids to determine if we have enough data
50
342
  count_query = select(
@@ -192,7 +484,31 @@ async def agent_action_cost(agent_id: str) -> Dict[str, Decimal]:
192
484
  return result
193
485
 
194
486
 
195
- async def update_agent_action_cost():
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."""
491
+
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)
496
+
497
+ if last_id:
498
+ query = query.where(AgentTable.id > last_id)
499
+
500
+ query = query.limit(batch_size)
501
+ result = await session.execute(query)
502
+ agent_ids = [row[0] for row in result]
503
+
504
+ if not agent_ids:
505
+ break
506
+
507
+ yield agent_ids
508
+ last_id = agent_ids[-1]
509
+
510
+
511
+ async def update_agent_action_cost(batch_size: int = 100) -> None:
196
512
  """
197
513
  Update action costs for all agents.
198
514
 
@@ -209,42 +525,20 @@ async def update_agent_action_cost():
209
525
  """
210
526
  logger.info("Starting update of agent average action costs")
211
527
  start_time = time.time()
212
- batch_size = 100
213
- last_id = None
214
528
  total_updated = 0
215
529
 
216
- while True:
217
- # Get a batch of agent IDs ordered by ID
218
- async with get_session() as session:
219
- query = select(AgentTable.id).order_by(AgentTable.id)
220
-
221
- # Apply pagination if we have a last_id from previous batch
222
- if last_id:
223
- query = query.where(AgentTable.id > last_id)
224
-
225
- query = query.limit(batch_size)
226
- result = await session.execute(query)
227
- agent_ids = [row[0] for row in result]
228
-
229
- # If no more agents, we're done
230
- if not agent_ids:
231
- break
232
-
233
- # Update last_id for next batch
234
- last_id = agent_ids[-1]
235
-
236
- # Process this batch of agents
530
+ async for agent_ids in _iterate_agent_id_batches(batch_size):
237
531
  logger.info(
238
- 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],
239
535
  )
240
536
  batch_start_time = time.time()
241
537
 
242
538
  for agent_id in agent_ids:
243
539
  try:
244
- # Calculate action costs for this agent
245
540
  costs = await agent_action_cost(agent_id)
246
541
 
247
- # Update the agent's quota record
248
542
  async with get_session() as session:
249
543
  update_stmt = (
250
544
  update(AgentQuotaTable)
@@ -262,203 +556,186 @@ async def update_agent_action_cost():
262
556
  await session.commit()
263
557
 
264
558
  total_updated += 1
265
- except Exception as e:
559
+ except Exception as e: # pragma: no cover - log path only
266
560
  logger.error(
267
- f"Error updating action costs for agent {agent_id}: {str(e)}"
561
+ "Error updating action costs for agent %s: %s", agent_id, str(e)
268
562
  )
269
563
 
270
564
  batch_time = time.time() - batch_start_time
271
- logger.info(f"Completed batch in {batch_time:.3f}s")
565
+ logger.info("Completed batch in %.3fs", batch_time)
272
566
 
273
567
  total_time = time.time() - start_time
274
568
  logger.info(
275
- 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,
276
572
  )
277
573
 
278
574
 
279
- async def list_autonomous_tasks(agent_id: str) -> List[AgentAutonomous]:
280
- """
281
- List all autonomous tasks for an agent.
575
+ async def update_agents_account_snapshot(batch_size: int = 100) -> None:
576
+ """Refresh the cached credit account snapshot for every agent."""
282
577
 
283
- Args:
284
- agent_id: ID of the agent
285
-
286
- Returns:
287
- 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
288
581
 
289
- Raises:
290
- IntentKitAPIError: If agent is not found
291
- """
292
- agent = await Agent.get(agent_id)
293
- if not agent:
294
- raise IntentKitAPIError(
295
- 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],
296
587
  )
588
+ batch_start_time = time.time()
297
589
 
298
- if not agent.autonomous:
299
- return []
300
-
301
- return agent.autonomous
302
-
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()
303
604
 
304
- async def add_autonomous_task(agent_id: str, task: AgentAutonomous) -> AgentAutonomous:
305
- """
306
- 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
+ )
307
612
 
308
- Args:
309
- agent_id: ID of the agent
310
- 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)
311
615
 
312
- Returns:
313
- 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
+ )
314
622
 
315
- Raises:
316
- IntentKitAPIError: If agent is not found
317
- """
318
- agent = await Agent.get(agent_id)
319
- if not agent:
320
- raise IntentKitAPIError(
321
- 400, "AgentNotFound", f"Agent with ID {agent_id} does not exist."
322
- )
323
623
 
324
- # Get current autonomous tasks
325
- current_tasks = agent.autonomous or []
326
- if not isinstance(current_tasks, list):
327
- current_tasks = []
624
+ async def update_agents_assets(batch_size: int = 100) -> None:
625
+ """Refresh cached asset information for all agents."""
328
626
 
329
- # Add the new task
330
- current_tasks.append(task)
627
+ from intentkit.core.asset import agent_asset
331
628
 
332
- # Convert all AgentAutonomous objects to dictionaries for JSON serialization
333
- 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
334
632
 
335
- # Update the agent in the database
336
- async with get_session() as session:
337
- update_stmt = (
338
- update(AgentTable)
339
- .where(AgentTable.id == agent_id)
340
- .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],
341
638
  )
342
- await session.execute(update_stmt)
343
- await session.commit()
344
-
345
- logger.info(f"Added autonomous task {task.id} to agent {agent_id}")
346
- return task
347
-
348
-
349
- async def delete_autonomous_task(agent_id: str, task_id: str) -> None:
350
- """
351
- Delete an autonomous task from an agent.
639
+ batch_start_time = time.time()
352
640
 
353
- Args:
354
- agent_id: ID of the agent
355
- 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
356
654
 
357
- Raises:
358
- IntentKitAPIError: If agent is not found or task is not found
359
- """
360
- agent = await Agent.get(agent_id)
361
- if not agent:
362
- raise IntentKitAPIError(
363
- 400, "AgentNotFound", f"Agent with ID {agent_id} does not exist."
364
- )
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()
365
663
 
366
- # Get current autonomous tasks
367
- current_tasks = agent.autonomous or []
368
- if not isinstance(current_tasks, list):
369
- current_tasks = []
370
-
371
- # Find and remove the task
372
- task_found = False
373
- updated_tasks = []
374
- for task_data in current_tasks:
375
- if task_data.id == task_id:
376
- task_found = True
377
- continue
378
- updated_tasks.append(task_data)
379
-
380
- if not task_found:
381
- raise IntentKitAPIError(
382
- 404, "TaskNotFound", f"Autonomous task with ID {task_id} not found."
383
- )
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
+ )
384
669
 
385
- # Convert remaining AgentAutonomous objects to dictionaries for JSON serialization
386
- 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)
387
672
 
388
- # Update the agent in the database
389
- async with get_session() as session:
390
- update_stmt = (
391
- update(AgentTable)
392
- .where(AgentTable.id == agent_id)
393
- .values(autonomous=serializable_tasks)
394
- )
395
- await session.execute(update_stmt)
396
- 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
+ )
397
679
 
398
- logger.info(f"Deleted autonomous task {task_id} from agent {agent_id}")
399
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."""
400
685
 
401
- async def update_autonomous_task(
402
- agent_id: str, task_id: str, task_updates: dict
403
- ) -> AgentAutonomous:
404
- """
405
- Update an autonomous task for an agent.
686
+ from intentkit.core.statistics import get_agent_statistics
406
687
 
407
- Args:
408
- agent_id: ID of the agent
409
- task_id: ID of the task to update
410
- 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)
411
694
 
412
- Returns:
413
- 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
414
698
 
415
- Raises:
416
- IntentKitAPIError: If agent is not found or task is not found
417
- """
418
- agent = await Agent.get(agent_id)
419
- if not agent:
420
- raise IntentKitAPIError(
421
- 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],
422
704
  )
705
+ batch_start_time = time.time()
423
706
 
424
- # Get current autonomous tasks
425
- current_tasks: List[AgentAutonomous] = agent.autonomous or []
426
-
427
- # Find and update the task
428
- task_found = False
429
- updated_tasks: List[AgentAutonomous] = []
430
- updated_task = None
431
-
432
- for task_data in current_tasks:
433
- if task_data.id == task_id:
434
- task_found = True
435
- # Create a dictionary with current task data
436
- task_dict = task_data.model_dump()
437
- # Update with provided fields
438
- task_dict.update(task_updates)
439
- # Create new AgentAutonomous instance
440
- updated_task = AgentAutonomous.model_validate(task_dict)
441
- updated_tasks.append(updated_task)
442
- else:
443
- 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
444
715
 
445
- if not task_found:
446
- raise IntentKitAPIError(
447
- 404, "TaskNotFound", f"Autonomous task with ID {task_id} not found."
448
- )
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()
449
724
 
450
- # Convert all AgentAutonomous objects to dictionaries for JSON serialization
451
- 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
+ )
452
732
 
453
- # Update the agent in the database
454
- async with get_session() as session:
455
- update_stmt = (
456
- update(AgentTable)
457
- .where(AgentTable.id == agent_id)
458
- .values(autonomous=serializable_tasks)
459
- )
460
- await session.execute(update_stmt)
461
- await session.commit()
733
+ batch_time = time.time() - batch_start_time
734
+ logger.info("Completed statistics batch in %.3fs", batch_time)
462
735
 
463
- logger.info(f"Updated autonomous task {task_id} for agent {agent_id}")
464
- 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
+ )