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/models/redis.py CHANGED
@@ -1,21 +1,20 @@
1
1
  """Redis client module for IntentKit."""
2
2
 
3
3
  import logging
4
- from typing import Optional
5
4
 
6
5
  from redis.asyncio import Redis
7
6
 
8
7
  logger = logging.getLogger(__name__)
9
8
 
10
9
  # Global Redis client instance
11
- _redis_client: Optional[Redis] = None
10
+ _redis_client: Redis | None = None
12
11
 
13
12
 
14
13
  async def init_redis(
15
14
  host: str,
16
15
  port: int = 6379,
17
16
  db: int = 0,
18
- password: Optional[str] = None,
17
+ password: str | None = None,
19
18
  ssl: bool = False,
20
19
  encoding: str = "utf-8",
21
20
  decode_responses: bool = True,
@@ -74,6 +73,9 @@ def get_redis() -> Redis:
74
73
  return _redis_client
75
74
 
76
75
 
76
+ DEFAULT_HEARTBEAT_TTL = 16 * 60
77
+
78
+
77
79
  async def send_heartbeat(redis_client: Redis, name: str) -> None:
78
80
  """Set a heartbeat key in Redis that expires after 16 minutes.
79
81
 
@@ -83,7 +85,7 @@ async def send_heartbeat(redis_client: Redis, name: str) -> None:
83
85
  """
84
86
  try:
85
87
  key = f"intentkit:heartbeat:{name}"
86
- await redis_client.set(key, 1, ex=190) # 190 seconds = 3 minutes
88
+ await redis_client.set(key, 1, ex=DEFAULT_HEARTBEAT_TTL)
87
89
  except Exception as e:
88
90
  logger.error(f"Failed to send heartbeat for {name}: {e}")
89
91
 
intentkit/models/skill.py CHANGED
@@ -1,15 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import csv
1
4
  import json
2
- from datetime import datetime, timezone
5
+ import logging
6
+ from datetime import UTC, datetime
3
7
  from decimal import Decimal
4
- from typing import Annotated, Any, Dict, Optional
8
+ from pathlib import Path
9
+ from typing import Annotated, Any
5
10
 
6
- from intentkit.models.base import Base
7
- from intentkit.models.db import get_session
8
- from intentkit.models.redis import get_redis
9
11
  from pydantic import BaseModel, ConfigDict, Field
10
12
  from sqlalchemy import (
11
13
  Boolean,
12
- Column,
13
14
  DateTime,
14
15
  Integer,
15
16
  Numeric,
@@ -19,6 +20,14 @@ from sqlalchemy import (
19
20
  select,
20
21
  )
21
22
  from sqlalchemy.dialects.postgresql import JSON, JSONB
23
+ from sqlalchemy.ext.asyncio import AsyncSession
24
+ from sqlalchemy.orm import Mapped, mapped_column
25
+
26
+ from intentkit.models.base import Base
27
+ from intentkit.models.db import get_session
28
+ from intentkit.models.redis import get_redis
29
+
30
+ logger = logging.getLogger(__name__)
22
31
 
23
32
 
24
33
  class AgentSkillDataTable(Base):
@@ -26,21 +35,23 @@ class AgentSkillDataTable(Base):
26
35
 
27
36
  __tablename__ = "agent_skill_data"
28
37
 
29
- agent_id = Column(String, primary_key=True)
30
- skill = Column(String, primary_key=True)
31
- key = Column(String, primary_key=True)
32
- data = Column(JSON().with_variant(JSONB(), "postgresql"), nullable=True)
33
- size = Column(Integer, nullable=False, default=0)
34
- created_at = Column(
38
+ agent_id: Mapped[str] = mapped_column(String, primary_key=True)
39
+ skill: Mapped[str] = mapped_column(String, primary_key=True)
40
+ key: Mapped[str] = mapped_column(String, primary_key=True)
41
+ data: Mapped[dict[str, Any] | None] = mapped_column(
42
+ JSON().with_variant(JSONB(), "postgresql"), nullable=True
43
+ )
44
+ size: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
45
+ created_at: Mapped[datetime] = mapped_column(
35
46
  DateTime(timezone=True),
36
47
  nullable=False,
37
48
  server_default=func.now(),
38
49
  )
39
- updated_at = Column(
50
+ updated_at: Mapped[datetime] = mapped_column(
40
51
  DateTime(timezone=True),
41
52
  nullable=False,
42
53
  server_default=func.now(),
43
- onupdate=lambda: datetime.now(timezone.utc),
54
+ onupdate=lambda: datetime.now(UTC),
44
55
  )
45
56
 
46
57
 
@@ -52,7 +63,7 @@ class AgentSkillDataCreate(BaseModel):
52
63
  agent_id: Annotated[str, Field(description="ID of the agent this data belongs to")]
53
64
  skill: Annotated[str, Field(description="Name of the skill this data is for")]
54
65
  key: Annotated[str, Field(description="Key for this specific piece of data")]
55
- data: Annotated[Dict[str, Any], Field(description="JSON data stored for this key")]
66
+ data: Annotated[dict[str, Any], Field(description="JSON data stored for this key")]
56
67
 
57
68
  async def save(self) -> "AgentSkillData":
58
69
  """Save or update skill data.
@@ -151,7 +162,7 @@ class AgentSkillData(AgentSkillDataCreate):
151
162
  return result or 0
152
163
 
153
164
  @classmethod
154
- async def get(cls, agent_id: str, skill: str, key: str) -> Optional[dict]:
165
+ async def get(cls, agent_id: str, skill: str, key: str) -> dict | None:
155
166
  """Get skill data for an agent.
156
167
 
157
168
  Args:
@@ -207,54 +218,54 @@ class AgentSkillData(AgentSkillDataCreate):
207
218
  await db.commit()
208
219
 
209
220
 
210
- class ThreadSkillDataTable(Base):
211
- """Database table model for storing skill-specific data for threads."""
221
+ class ChatSkillDataTable(Base):
222
+ """Database table model for storing skill-specific data for chats."""
212
223
 
213
- __tablename__ = "thread_skill_data"
224
+ __tablename__ = "chat_skill_data"
214
225
 
215
- thread_id = Column(String, primary_key=True)
216
- skill = Column(String, primary_key=True)
217
- key = Column(String, primary_key=True)
218
- agent_id = Column(String, nullable=False)
219
- data = Column(JSON().with_variant(JSONB(), "postgresql"), nullable=True)
220
- created_at = Column(
226
+ chat_id: Mapped[str] = mapped_column(String, primary_key=True)
227
+ skill: Mapped[str] = mapped_column(String, primary_key=True)
228
+ key: Mapped[str] = mapped_column(String, primary_key=True)
229
+ agent_id: Mapped[str] = mapped_column(String, nullable=False)
230
+ data: Mapped[dict[str, Any] | None] = mapped_column(
231
+ JSON().with_variant(JSONB(), "postgresql"), nullable=True
232
+ )
233
+ created_at: Mapped[datetime] = mapped_column(
221
234
  DateTime(timezone=True),
222
235
  nullable=False,
223
236
  server_default=func.now(),
224
237
  )
225
- updated_at = Column(
238
+ updated_at: Mapped[datetime] = mapped_column(
226
239
  DateTime(timezone=True),
227
240
  nullable=False,
228
241
  server_default=func.now(),
229
- onupdate=lambda: datetime.now(timezone.utc),
242
+ onupdate=lambda: datetime.now(UTC),
230
243
  )
231
244
 
232
245
 
233
- class ThreadSkillDataCreate(BaseModel):
234
- """Base model for creating thread skill data records."""
246
+ class ChatSkillDataCreate(BaseModel):
247
+ """Base model for creating chat skill data records."""
235
248
 
236
249
  model_config = ConfigDict(from_attributes=True)
237
250
 
238
- thread_id: Annotated[
239
- str, Field(description="ID of the thread this data belongs to")
240
- ]
251
+ chat_id: Annotated[str, Field(description="ID of the chat this data belongs to")]
241
252
  skill: Annotated[str, Field(description="Name of the skill this data is for")]
242
253
  key: Annotated[str, Field(description="Key for this specific piece of data")]
243
- agent_id: Annotated[str, Field(description="ID of the agent that owns this thread")]
244
- data: Annotated[Dict[str, Any], Field(description="JSON data stored for this key")]
254
+ agent_id: Annotated[str, Field(description="ID of the agent that owns this chat")]
255
+ data: Annotated[dict[str, Any], Field(description="JSON data stored for this key")]
245
256
 
246
- async def save(self) -> "ThreadSkillData":
257
+ async def save(self) -> "ChatSkillData":
247
258
  """Save or update skill data.
248
259
 
249
260
  Returns:
250
- ThreadSkillData: The saved thread skill data instance
261
+ ChatSkillData: The saved chat skill data instance
251
262
  """
252
263
  async with get_session() as db:
253
264
  record = await db.scalar(
254
- select(ThreadSkillDataTable).where(
255
- ThreadSkillDataTable.thread_id == self.thread_id,
256
- ThreadSkillDataTable.skill == self.skill,
257
- ThreadSkillDataTable.key == self.key,
265
+ select(ChatSkillDataTable).where(
266
+ ChatSkillDataTable.chat_id == self.chat_id,
267
+ ChatSkillDataTable.skill == self.skill,
268
+ ChatSkillDataTable.key == self.key,
258
269
  )
259
270
  )
260
271
 
@@ -264,18 +275,18 @@ class ThreadSkillDataCreate(BaseModel):
264
275
  record.agent_id = self.agent_id
265
276
  else:
266
277
  # Create new record
267
- record = ThreadSkillDataTable(**self.model_dump())
278
+ record = ChatSkillDataTable(**self.model_dump())
268
279
  db.add(record)
269
280
  await db.commit()
270
281
  await db.refresh(record)
271
- return ThreadSkillData.model_validate(record)
282
+ return ChatSkillData.model_validate(record)
272
283
 
273
284
 
274
- class ThreadSkillData(ThreadSkillDataCreate):
275
- """Model for storing skill-specific data for threads.
285
+ class ChatSkillData(ChatSkillDataCreate):
286
+ """Model for storing skill-specific data for chats.
276
287
 
277
- This model uses a composite primary key of (thread_id, skill, key) to store
278
- skill-specific data for threads in a flexible way. It also includes agent_id
288
+ This model uses a composite primary key of (chat_id, skill, key) to store
289
+ skill-specific data for chats in a flexible way. It also includes agent_id
279
290
  as a required field for tracking ownership.
280
291
  """
281
292
 
@@ -292,11 +303,11 @@ class ThreadSkillData(ThreadSkillDataCreate):
292
303
  ]
293
304
 
294
305
  @classmethod
295
- async def get(cls, thread_id: str, skill: str, key: str) -> Optional[dict]:
296
- """Get skill data for a thread.
306
+ async def get(cls, chat_id: str, skill: str, key: str) -> dict | None:
307
+ """Get skill data for a chat.
297
308
 
298
309
  Args:
299
- thread_id: ID of the thread
310
+ chat_id: ID of the chat
300
311
  skill: Name of the skill
301
312
  key: Data key
302
313
 
@@ -305,10 +316,10 @@ class ThreadSkillData(ThreadSkillDataCreate):
305
316
  """
306
317
  async with get_session() as db:
307
318
  record = await db.scalar(
308
- select(ThreadSkillDataTable).where(
309
- ThreadSkillDataTable.thread_id == thread_id,
310
- ThreadSkillDataTable.skill == skill,
311
- ThreadSkillDataTable.key == key,
319
+ select(ChatSkillDataTable).where(
320
+ ChatSkillDataTable.chat_id == chat_id,
321
+ ChatSkillDataTable.skill == skill,
322
+ ChatSkillDataTable.key == key,
312
323
  )
313
324
  )
314
325
  return record.data if record else None
@@ -317,63 +328,135 @@ class ThreadSkillData(ThreadSkillDataCreate):
317
328
  async def clean_data(
318
329
  cls,
319
330
  agent_id: str,
320
- thread_id: Annotated[
331
+ chat_id: Annotated[
321
332
  str,
322
333
  Field(
323
334
  default="",
324
- description="Optional ID of the thread. If provided, only cleans data for that thread.",
335
+ description="Optional ID of the chat. If provided, only cleans data for that chat.",
325
336
  ),
326
337
  ],
327
338
  ):
328
- """Clean all skill data for a thread or agent.
339
+ """Clean all skill data for a chat or agent.
329
340
 
330
341
  Args:
331
342
  agent_id: ID of the agent
332
- thread_id: Optional ID of the thread. If provided, only cleans data for that thread.
333
- If empty, cleans all data for the agent.
343
+ chat_id: Optional ID of the chat. If provided, only cleans data for that chat.
344
+ If empty, cleans all data for the agent.
334
345
  """
335
346
  async with get_session() as db:
336
- if thread_id and thread_id != "":
347
+ if chat_id and chat_id != "":
337
348
  await db.execute(
338
- delete(ThreadSkillDataTable).where(
339
- ThreadSkillDataTable.agent_id == agent_id,
340
- ThreadSkillDataTable.thread_id == thread_id,
349
+ delete(ChatSkillDataTable).where(
350
+ ChatSkillDataTable.agent_id == agent_id,
351
+ ChatSkillDataTable.chat_id == chat_id,
341
352
  )
342
353
  )
343
354
  else:
344
355
  await db.execute(
345
- delete(ThreadSkillDataTable).where(
346
- ThreadSkillDataTable.agent_id == agent_id
356
+ delete(ChatSkillDataTable).where(
357
+ ChatSkillDataTable.agent_id == agent_id
347
358
  )
348
359
  )
349
360
  await db.commit()
350
361
 
351
362
 
363
+ def _skill_parse_bool(value: str | None) -> bool:
364
+ if value is None:
365
+ return False
366
+ return value.strip().lower() in {"true", "1", "yes"}
367
+
368
+
369
+ def _skill_parse_optional_int(value: str | None) -> int | None:
370
+ if value is None:
371
+ return None
372
+ value = value.strip()
373
+ return int(value) if value else None
374
+
375
+
376
+ def _skill_parse_decimal(value: str | None, default: str = "0") -> Decimal:
377
+ value = (value or "").strip()
378
+ if not value:
379
+ value = default
380
+ return Decimal(value)
381
+
382
+
383
+ def _load_default_skills() -> tuple[dict[str, "Skill"], dict[tuple[str, str], "Skill"]]:
384
+ """Load default skills from CSV into lookup maps."""
385
+
386
+ path = Path(__file__).with_name("skills.csv")
387
+ if not path.exists():
388
+ logger.warning("Default skills CSV not found at %s", path)
389
+ return {}, {}
390
+
391
+ by_name: dict[str, Skill] = {}
392
+ by_category_config: dict[tuple[str, str], Skill] = {}
393
+
394
+ with path.open(newline="", encoding="utf-8") as csvfile:
395
+ reader = csv.DictReader(csvfile)
396
+ for row in reader:
397
+ try:
398
+ timestamp = datetime.now(UTC)
399
+ price_default = row.get("price") or "1"
400
+ skill = Skill(
401
+ name=row["name"],
402
+ category=row["category"],
403
+ config_name=row.get("config_name") or None,
404
+ enabled=_skill_parse_bool(row.get("enabled")),
405
+ price_level=_skill_parse_optional_int(row.get("price_level")),
406
+ price=_skill_parse_decimal(row.get("price"), default="1"),
407
+ price_self_key=_skill_parse_decimal(
408
+ row.get("price_self_key"), default=price_default
409
+ ),
410
+ rate_limit_count=_skill_parse_optional_int(
411
+ row.get("rate_limit_count")
412
+ ),
413
+ rate_limit_minutes=_skill_parse_optional_int(
414
+ row.get("rate_limit_minutes")
415
+ ),
416
+ author=row.get("author") or None,
417
+ created_at=timestamp,
418
+ updated_at=timestamp,
419
+ )
420
+ except Exception as exc:
421
+ logger.error(
422
+ "Failed to load default skill %s: %s", row.get("name"), exc
423
+ )
424
+ continue
425
+
426
+ by_name[skill.name] = skill
427
+ if skill.config_name:
428
+ by_category_config[(skill.category, skill.config_name)] = skill
429
+
430
+ return by_name, by_category_config
431
+
432
+
352
433
  class SkillTable(Base):
353
434
  """Database table model for Skill."""
354
435
 
355
436
  __tablename__ = "skills"
356
437
 
357
- name = Column(String, primary_key=True)
358
- enabled = Column(Boolean, nullable=False, default=True)
359
- category = Column(String, nullable=False)
360
- config_name = Column(String, nullable=True)
361
- price_level = Column(Integer, nullable=True)
362
- price = Column(Numeric(22, 4), nullable=False, default=1)
363
- price_self_key = Column(Numeric(22, 4), nullable=False, default=1)
364
- rate_limit_count = Column(Integer, nullable=True)
365
- rate_limit_minutes = Column(Integer, nullable=True)
366
- author = Column(String, nullable=True)
367
- created_at = Column(
438
+ name: Mapped[str] = mapped_column(String, primary_key=True)
439
+ enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
440
+ category: Mapped[str] = mapped_column(String, nullable=False)
441
+ config_name: Mapped[str | None] = mapped_column(String, nullable=True)
442
+ price_level: Mapped[int | None] = mapped_column(Integer, nullable=True)
443
+ price: Mapped[Decimal] = mapped_column(Numeric(22, 4), nullable=False, default=1)
444
+ price_self_key: Mapped[Decimal] = mapped_column(
445
+ Numeric(22, 4), nullable=False, default=1
446
+ )
447
+ rate_limit_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
448
+ rate_limit_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
449
+ author: Mapped[str | None] = mapped_column(String, nullable=True)
450
+ created_at: Mapped[datetime] = mapped_column(
368
451
  DateTime(timezone=True),
369
452
  nullable=False,
370
453
  server_default=func.now(),
371
454
  )
372
- updated_at = Column(
455
+ updated_at: Mapped[datetime] = mapped_column(
373
456
  DateTime(timezone=True),
374
457
  nullable=False,
375
458
  server_default=func.now(),
376
- onupdate=lambda: datetime.now(timezone.utc),
459
+ onupdate=lambda: datetime.now(UTC),
377
460
  )
378
461
 
379
462
 
@@ -390,10 +473,8 @@ class Skill(BaseModel):
390
473
  name: Annotated[str, Field(description="Name of the skill")]
391
474
  enabled: Annotated[bool, Field(description="Is this skill enabled?")]
392
475
  category: Annotated[str, Field(description="Category of the skill")]
393
- config_name: Annotated[Optional[str], Field(description="Config name of the skill")]
394
- price_level: Annotated[
395
- Optional[int], Field(description="Price level for this skill")
396
- ]
476
+ config_name: Annotated[str | None, Field(description="Config name of the skill")]
477
+ price_level: Annotated[int | None, Field(description="Price level for this skill")]
397
478
  price: Annotated[
398
479
  Decimal, Field(description="Price for this skill", default=Decimal("1"))
399
480
  ]
@@ -401,11 +482,9 @@ class Skill(BaseModel):
401
482
  Decimal,
402
483
  Field(description="Price for this skill with self key", default=Decimal("1")),
403
484
  ]
404
- rate_limit_count: Annotated[Optional[int], Field(description="Rate limit count")]
405
- rate_limit_minutes: Annotated[
406
- Optional[int], Field(description="Rate limit minutes")
407
- ]
408
- author: Annotated[Optional[str], Field(description="Author of the skill")]
485
+ rate_limit_count: Annotated[int | None, Field(description="Rate limit count")]
486
+ rate_limit_minutes: Annotated[int | None, Field(description="Rate limit minutes")]
487
+ author: Annotated[str | None, Field(description="Author of the skill")]
409
488
  created_at: Annotated[
410
489
  datetime, Field(description="Timestamp when this record was created")
411
490
  ]
@@ -414,7 +493,7 @@ class Skill(BaseModel):
414
493
  ]
415
494
 
416
495
  @staticmethod
417
- async def get(name: str) -> Optional["Skill"]:
496
+ async def get(name: str) -> Skill | None:
418
497
  """Get a skill by name with Redis caching.
419
498
 
420
499
  The skill is cached in Redis for 3 minutes.
@@ -447,20 +526,23 @@ class Skill(BaseModel):
447
526
  stmt = select(SkillTable).where(SkillTable.name == name)
448
527
  skill = await session.scalar(stmt)
449
528
 
450
- # If skill doesn't exist, return None
451
- if not skill:
452
- return None
453
-
454
- # Convert to Skill model
455
- skill_model = Skill.model_validate(skill)
529
+ # If skill exists in database, convert and cache it
530
+ if skill:
531
+ skill_model = Skill.model_validate(skill)
532
+ await redis.set(cache_key, skill_model.model_dump_json(), ex=cache_ttl)
533
+ return skill_model
456
534
 
457
- # Cache the skill in Redis
535
+ # Fallback to default skills loaded from CSV
536
+ default_skill = DEFAULT_SKILLS_BY_NAME.get(name)
537
+ if default_skill:
538
+ skill_model = default_skill.model_copy(deep=True)
458
539
  await redis.set(cache_key, skill_model.model_dump_json(), ex=cache_ttl)
459
-
460
540
  return skill_model
461
541
 
542
+ return None
543
+
462
544
  @staticmethod
463
- async def get_by_config_name(category: str, config_name: str) -> Optional["Skill"]:
545
+ async def get_by_config_name(category: str, config_name: str) -> "Skill" | None:
464
546
  """Get a skill by category and config_name.
465
547
 
466
548
  Args:
@@ -477,9 +559,48 @@ class Skill(BaseModel):
477
559
  )
478
560
  skill = await session.scalar(stmt)
479
561
 
480
- # If skill doesn't exist, return None
481
- if not skill:
482
- return None
562
+ # If skill exists in database, return it
563
+ if skill:
564
+ return Skill.model_validate(skill)
565
+
566
+ # Fallback to default skills loaded from CSV
567
+ default_skill = DEFAULT_SKILLS_BY_CATEGORY_CONFIG.get((category, config_name))
568
+ if default_skill:
569
+ return default_skill.model_copy(deep=True)
570
+
571
+ return None
572
+
573
+ @classmethod
574
+ async def get_all(cls, session: AsyncSession | None = None) -> list["Skill"]:
575
+ """Return all skills merged from defaults and database overrides."""
576
+
577
+ if session is None:
578
+ async with get_session() as db:
579
+ return await cls.get_all(session=db)
580
+
581
+ skills: dict[str, Skill] = {
582
+ name: skill.model_copy(deep=True)
583
+ for name, skill in DEFAULT_SKILLS_BY_NAME.items()
584
+ }
585
+
586
+ result = await session.execute(select(SkillTable))
587
+ for row in result.scalars():
588
+ skill_model = cls.model_validate(row)
589
+
590
+ default_skill = DEFAULT_SKILLS_BY_NAME.get(skill_model.name)
591
+ if default_skill is not None:
592
+ # Merge database overrides with default skill configuration while
593
+ # keeping default values for fields that are omitted in the
594
+ # database (e.g. config_name).
595
+ skill_model = default_skill.model_copy(
596
+ update=skill_model.model_dump(exclude_none=True),
597
+ deep=True,
598
+ )
599
+
600
+ skills[skill_model.name] = skill_model
601
+
602
+ return list(skills.values())
603
+
483
604
 
484
- # Convert to Skill model
485
- return Skill.model_validate(skill)
605
+ # Default skills loaded from CSV
606
+ DEFAULT_SKILLS_BY_NAME, DEFAULT_SKILLS_BY_CATEGORY_CONFIG = _load_default_skills()