intentkit 0.5.0__py3-none-any.whl → 0.5.2__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 (366) hide show
  1. intentkit/__init__.py +17 -0
  2. intentkit/abstracts/__init__.py +0 -0
  3. intentkit/abstracts/agent.py +60 -0
  4. intentkit/abstracts/api.py +4 -0
  5. intentkit/abstracts/engine.py +38 -0
  6. intentkit/abstracts/exception.py +9 -0
  7. intentkit/abstracts/graph.py +25 -0
  8. intentkit/abstracts/skill.py +129 -0
  9. intentkit/abstracts/twitter.py +54 -0
  10. intentkit/clients/__init__.py +14 -0
  11. intentkit/clients/cdp.py +53 -0
  12. intentkit/clients/twitter.py +445 -0
  13. intentkit/config/__init__.py +0 -0
  14. intentkit/config/config.py +164 -0
  15. intentkit/core/__init__.py +0 -0
  16. intentkit/core/agent.py +191 -0
  17. intentkit/core/api.py +40 -0
  18. intentkit/core/client.py +45 -0
  19. intentkit/core/credit.py +1767 -0
  20. intentkit/core/engine.py +1018 -0
  21. intentkit/core/node.py +223 -0
  22. intentkit/core/prompt.py +58 -0
  23. intentkit/core/skill.py +124 -0
  24. intentkit/models/agent.py +1689 -0
  25. intentkit/models/agent_data.py +810 -0
  26. intentkit/models/agent_schema.json +733 -0
  27. intentkit/models/app_setting.py +156 -0
  28. intentkit/models/base.py +9 -0
  29. intentkit/models/chat.py +581 -0
  30. intentkit/models/conversation.py +286 -0
  31. intentkit/models/credit.py +1406 -0
  32. intentkit/models/db.py +120 -0
  33. intentkit/models/db_mig.py +102 -0
  34. intentkit/models/generator.py +347 -0
  35. intentkit/models/llm.py +746 -0
  36. intentkit/models/redis.py +132 -0
  37. intentkit/models/skill.py +466 -0
  38. intentkit/models/user.py +243 -0
  39. intentkit/skills/__init__.py +12 -0
  40. intentkit/skills/acolyt/__init__.py +83 -0
  41. intentkit/skills/acolyt/acolyt.jpg +0 -0
  42. intentkit/skills/acolyt/ask.py +128 -0
  43. intentkit/skills/acolyt/base.py +28 -0
  44. intentkit/skills/acolyt/schema.json +89 -0
  45. intentkit/skills/aixbt/README.md +71 -0
  46. intentkit/skills/aixbt/__init__.py +73 -0
  47. intentkit/skills/aixbt/aixbt.jpg +0 -0
  48. intentkit/skills/aixbt/base.py +21 -0
  49. intentkit/skills/aixbt/projects.py +153 -0
  50. intentkit/skills/aixbt/schema.json +99 -0
  51. intentkit/skills/allora/__init__.py +83 -0
  52. intentkit/skills/allora/allora.jpeg +0 -0
  53. intentkit/skills/allora/base.py +28 -0
  54. intentkit/skills/allora/price.py +130 -0
  55. intentkit/skills/allora/schema.json +89 -0
  56. intentkit/skills/base.py +174 -0
  57. intentkit/skills/carv/README.md +95 -0
  58. intentkit/skills/carv/__init__.py +121 -0
  59. intentkit/skills/carv/base.py +183 -0
  60. intentkit/skills/carv/carv.webp +0 -0
  61. intentkit/skills/carv/fetch_news.py +92 -0
  62. intentkit/skills/carv/onchain_query.py +164 -0
  63. intentkit/skills/carv/schema.json +137 -0
  64. intentkit/skills/carv/token_info_and_price.py +110 -0
  65. intentkit/skills/cdp/__init__.py +137 -0
  66. intentkit/skills/cdp/base.py +21 -0
  67. intentkit/skills/cdp/cdp.png +0 -0
  68. intentkit/skills/cdp/get_balance.py +81 -0
  69. intentkit/skills/cdp/schema.json +473 -0
  70. intentkit/skills/chainlist/README.md +38 -0
  71. intentkit/skills/chainlist/__init__.py +54 -0
  72. intentkit/skills/chainlist/base.py +21 -0
  73. intentkit/skills/chainlist/chain_lookup.py +208 -0
  74. intentkit/skills/chainlist/chainlist.png +0 -0
  75. intentkit/skills/chainlist/schema.json +47 -0
  76. intentkit/skills/common/__init__.py +82 -0
  77. intentkit/skills/common/base.py +21 -0
  78. intentkit/skills/common/common.jpg +0 -0
  79. intentkit/skills/common/current_time.py +84 -0
  80. intentkit/skills/common/schema.json +57 -0
  81. intentkit/skills/cookiefun/README.md +121 -0
  82. intentkit/skills/cookiefun/__init__.py +78 -0
  83. intentkit/skills/cookiefun/base.py +41 -0
  84. intentkit/skills/cookiefun/constants.py +18 -0
  85. intentkit/skills/cookiefun/cookiefun.png +0 -0
  86. intentkit/skills/cookiefun/get_account_details.py +171 -0
  87. intentkit/skills/cookiefun/get_account_feed.py +282 -0
  88. intentkit/skills/cookiefun/get_account_smart_followers.py +181 -0
  89. intentkit/skills/cookiefun/get_sectors.py +128 -0
  90. intentkit/skills/cookiefun/schema.json +155 -0
  91. intentkit/skills/cookiefun/search_accounts.py +225 -0
  92. intentkit/skills/cryptocompare/__init__.py +130 -0
  93. intentkit/skills/cryptocompare/api.py +159 -0
  94. intentkit/skills/cryptocompare/base.py +303 -0
  95. intentkit/skills/cryptocompare/cryptocompare.png +0 -0
  96. intentkit/skills/cryptocompare/fetch_news.py +96 -0
  97. intentkit/skills/cryptocompare/fetch_price.py +99 -0
  98. intentkit/skills/cryptocompare/fetch_top_exchanges.py +113 -0
  99. intentkit/skills/cryptocompare/fetch_top_market_cap.py +109 -0
  100. intentkit/skills/cryptocompare/fetch_top_volume.py +108 -0
  101. intentkit/skills/cryptocompare/fetch_trading_signals.py +107 -0
  102. intentkit/skills/cryptocompare/schema.json +168 -0
  103. intentkit/skills/cryptopanic/__init__.py +108 -0
  104. intentkit/skills/cryptopanic/base.py +51 -0
  105. intentkit/skills/cryptopanic/cryptopanic.png +0 -0
  106. intentkit/skills/cryptopanic/fetch_crypto_news.py +153 -0
  107. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +136 -0
  108. intentkit/skills/cryptopanic/schema.json +103 -0
  109. intentkit/skills/dapplooker/README.md +92 -0
  110. intentkit/skills/dapplooker/__init__.py +83 -0
  111. intentkit/skills/dapplooker/base.py +26 -0
  112. intentkit/skills/dapplooker/dapplooker.jpg +0 -0
  113. intentkit/skills/dapplooker/dapplooker_token_data.py +476 -0
  114. intentkit/skills/dapplooker/schema.json +91 -0
  115. intentkit/skills/defillama/__init__.py +323 -0
  116. intentkit/skills/defillama/api.py +315 -0
  117. intentkit/skills/defillama/base.py +135 -0
  118. intentkit/skills/defillama/coins/__init__.py +0 -0
  119. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +116 -0
  120. intentkit/skills/defillama/coins/fetch_block.py +98 -0
  121. intentkit/skills/defillama/coins/fetch_current_prices.py +105 -0
  122. intentkit/skills/defillama/coins/fetch_first_price.py +100 -0
  123. intentkit/skills/defillama/coins/fetch_historical_prices.py +110 -0
  124. intentkit/skills/defillama/coins/fetch_price_chart.py +109 -0
  125. intentkit/skills/defillama/coins/fetch_price_percentage.py +93 -0
  126. intentkit/skills/defillama/config/__init__.py +0 -0
  127. intentkit/skills/defillama/config/chains.py +433 -0
  128. intentkit/skills/defillama/defillama.jpeg +0 -0
  129. intentkit/skills/defillama/fees/__init__.py +0 -0
  130. intentkit/skills/defillama/fees/fetch_fees_overview.py +130 -0
  131. intentkit/skills/defillama/schema.json +383 -0
  132. intentkit/skills/defillama/stablecoins/__init__.py +0 -0
  133. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +100 -0
  134. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +129 -0
  135. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +83 -0
  136. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +126 -0
  137. intentkit/skills/defillama/tests/__init__.py +0 -0
  138. intentkit/skills/defillama/tests/api_integration.test.py +192 -0
  139. intentkit/skills/defillama/tests/api_unit.test.py +583 -0
  140. intentkit/skills/defillama/tvl/__init__.py +0 -0
  141. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +106 -0
  142. intentkit/skills/defillama/tvl/fetch_chains.py +107 -0
  143. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +91 -0
  144. intentkit/skills/defillama/tvl/fetch_protocol.py +207 -0
  145. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +93 -0
  146. intentkit/skills/defillama/tvl/fetch_protocols.py +196 -0
  147. intentkit/skills/defillama/volumes/__init__.py +0 -0
  148. intentkit/skills/defillama/volumes/fetch_dex_overview.py +157 -0
  149. intentkit/skills/defillama/volumes/fetch_dex_summary.py +123 -0
  150. intentkit/skills/defillama/volumes/fetch_options_overview.py +131 -0
  151. intentkit/skills/defillama/yields/__init__.py +0 -0
  152. intentkit/skills/defillama/yields/fetch_pool_chart.py +100 -0
  153. intentkit/skills/defillama/yields/fetch_pools.py +126 -0
  154. intentkit/skills/dexscreener/__init__.py +93 -0
  155. intentkit/skills/dexscreener/base.py +133 -0
  156. intentkit/skills/dexscreener/dexscreener.png +0 -0
  157. intentkit/skills/dexscreener/model/__init__.py +0 -0
  158. intentkit/skills/dexscreener/model/search_token_response.py +82 -0
  159. intentkit/skills/dexscreener/schema.json +48 -0
  160. intentkit/skills/dexscreener/search_token.py +321 -0
  161. intentkit/skills/dune_analytics/__init__.py +103 -0
  162. intentkit/skills/dune_analytics/base.py +46 -0
  163. intentkit/skills/dune_analytics/dune.png +0 -0
  164. intentkit/skills/dune_analytics/fetch_kol_buys.py +128 -0
  165. intentkit/skills/dune_analytics/fetch_nation_metrics.py +237 -0
  166. intentkit/skills/dune_analytics/schema.json +99 -0
  167. intentkit/skills/elfa/README.md +100 -0
  168. intentkit/skills/elfa/__init__.py +123 -0
  169. intentkit/skills/elfa/base.py +28 -0
  170. intentkit/skills/elfa/elfa.jpg +0 -0
  171. intentkit/skills/elfa/mention.py +504 -0
  172. intentkit/skills/elfa/schema.json +153 -0
  173. intentkit/skills/elfa/stats.py +118 -0
  174. intentkit/skills/elfa/tokens.py +126 -0
  175. intentkit/skills/enso/README.md +75 -0
  176. intentkit/skills/enso/__init__.py +114 -0
  177. intentkit/skills/enso/abi/__init__.py +0 -0
  178. intentkit/skills/enso/abi/approval.py +279 -0
  179. intentkit/skills/enso/abi/erc20.py +14 -0
  180. intentkit/skills/enso/abi/route.py +129 -0
  181. intentkit/skills/enso/base.py +44 -0
  182. intentkit/skills/enso/best_yield.py +286 -0
  183. intentkit/skills/enso/enso.jpg +0 -0
  184. intentkit/skills/enso/networks.py +105 -0
  185. intentkit/skills/enso/prices.py +93 -0
  186. intentkit/skills/enso/route.py +300 -0
  187. intentkit/skills/enso/schema.json +212 -0
  188. intentkit/skills/enso/tokens.py +223 -0
  189. intentkit/skills/enso/wallet.py +381 -0
  190. intentkit/skills/github/README.md +63 -0
  191. intentkit/skills/github/__init__.py +54 -0
  192. intentkit/skills/github/base.py +21 -0
  193. intentkit/skills/github/github.jpg +0 -0
  194. intentkit/skills/github/github_search.py +183 -0
  195. intentkit/skills/github/schema.json +59 -0
  196. intentkit/skills/heurist/__init__.py +143 -0
  197. intentkit/skills/heurist/base.py +26 -0
  198. intentkit/skills/heurist/heurist.png +0 -0
  199. intentkit/skills/heurist/image_generation_animagine_xl.py +162 -0
  200. intentkit/skills/heurist/image_generation_arthemy_comics.py +162 -0
  201. intentkit/skills/heurist/image_generation_arthemy_real.py +162 -0
  202. intentkit/skills/heurist/image_generation_braindance.py +162 -0
  203. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +162 -0
  204. intentkit/skills/heurist/image_generation_flux_1_dev.py +162 -0
  205. intentkit/skills/heurist/image_generation_sdxl.py +161 -0
  206. intentkit/skills/heurist/schema.json +196 -0
  207. intentkit/skills/lifi/README.md +294 -0
  208. intentkit/skills/lifi/__init__.py +141 -0
  209. intentkit/skills/lifi/base.py +21 -0
  210. intentkit/skills/lifi/lifi.png +0 -0
  211. intentkit/skills/lifi/schema.json +89 -0
  212. intentkit/skills/lifi/token_execute.py +472 -0
  213. intentkit/skills/lifi/token_quote.py +190 -0
  214. intentkit/skills/lifi/utils.py +656 -0
  215. intentkit/skills/moralis/README.md +490 -0
  216. intentkit/skills/moralis/__init__.py +110 -0
  217. intentkit/skills/moralis/api.py +281 -0
  218. intentkit/skills/moralis/base.py +55 -0
  219. intentkit/skills/moralis/fetch_chain_portfolio.py +191 -0
  220. intentkit/skills/moralis/fetch_nft_portfolio.py +284 -0
  221. intentkit/skills/moralis/fetch_solana_portfolio.py +331 -0
  222. intentkit/skills/moralis/fetch_wallet_portfolio.py +301 -0
  223. intentkit/skills/moralis/moralis.png +0 -0
  224. intentkit/skills/moralis/schema.json +156 -0
  225. intentkit/skills/moralis/tests/__init__.py +0 -0
  226. intentkit/skills/moralis/tests/test_wallet.py +511 -0
  227. intentkit/skills/nation/__init__.py +62 -0
  228. intentkit/skills/nation/base.py +31 -0
  229. intentkit/skills/nation/nation.png +0 -0
  230. intentkit/skills/nation/nft_check.py +106 -0
  231. intentkit/skills/nation/schema.json +58 -0
  232. intentkit/skills/openai/__init__.py +107 -0
  233. intentkit/skills/openai/base.py +32 -0
  234. intentkit/skills/openai/dalle_image_generation.py +128 -0
  235. intentkit/skills/openai/gpt_image_generation.py +152 -0
  236. intentkit/skills/openai/gpt_image_to_image.py +186 -0
  237. intentkit/skills/openai/image_to_text.py +126 -0
  238. intentkit/skills/openai/openai.png +0 -0
  239. intentkit/skills/openai/schema.json +139 -0
  240. intentkit/skills/portfolio/README.md +55 -0
  241. intentkit/skills/portfolio/__init__.py +151 -0
  242. intentkit/skills/portfolio/base.py +107 -0
  243. intentkit/skills/portfolio/constants.py +9 -0
  244. intentkit/skills/portfolio/moralis.png +0 -0
  245. intentkit/skills/portfolio/schema.json +237 -0
  246. intentkit/skills/portfolio/token_balances.py +155 -0
  247. intentkit/skills/portfolio/wallet_approvals.py +102 -0
  248. intentkit/skills/portfolio/wallet_defi_positions.py +80 -0
  249. intentkit/skills/portfolio/wallet_history.py +155 -0
  250. intentkit/skills/portfolio/wallet_net_worth.py +112 -0
  251. intentkit/skills/portfolio/wallet_nfts.py +139 -0
  252. intentkit/skills/portfolio/wallet_profitability.py +101 -0
  253. intentkit/skills/portfolio/wallet_profitability_summary.py +91 -0
  254. intentkit/skills/portfolio/wallet_stats.py +79 -0
  255. intentkit/skills/portfolio/wallet_swaps.py +147 -0
  256. intentkit/skills/skills.toml +103 -0
  257. intentkit/skills/slack/__init__.py +98 -0
  258. intentkit/skills/slack/base.py +55 -0
  259. intentkit/skills/slack/get_channel.py +109 -0
  260. intentkit/skills/slack/get_message.py +136 -0
  261. intentkit/skills/slack/schedule_message.py +92 -0
  262. intentkit/skills/slack/schema.json +135 -0
  263. intentkit/skills/slack/send_message.py +81 -0
  264. intentkit/skills/slack/slack.jpg +0 -0
  265. intentkit/skills/system/__init__.py +90 -0
  266. intentkit/skills/system/base.py +22 -0
  267. intentkit/skills/system/read_agent_api_key.py +87 -0
  268. intentkit/skills/system/regenerate_agent_api_key.py +77 -0
  269. intentkit/skills/system/schema.json +53 -0
  270. intentkit/skills/system/system.svg +76 -0
  271. intentkit/skills/tavily/README.md +86 -0
  272. intentkit/skills/tavily/__init__.py +91 -0
  273. intentkit/skills/tavily/base.py +27 -0
  274. intentkit/skills/tavily/schema.json +119 -0
  275. intentkit/skills/tavily/tavily.jpg +0 -0
  276. intentkit/skills/tavily/tavily_extract.py +147 -0
  277. intentkit/skills/tavily/tavily_search.py +139 -0
  278. intentkit/skills/token/README.md +89 -0
  279. intentkit/skills/token/__init__.py +107 -0
  280. intentkit/skills/token/base.py +154 -0
  281. intentkit/skills/token/constants.py +9 -0
  282. intentkit/skills/token/erc20_transfers.py +145 -0
  283. intentkit/skills/token/moralis.png +0 -0
  284. intentkit/skills/token/schema.json +141 -0
  285. intentkit/skills/token/token_analytics.py +81 -0
  286. intentkit/skills/token/token_price.py +132 -0
  287. intentkit/skills/token/token_search.py +121 -0
  288. intentkit/skills/twitter/__init__.py +146 -0
  289. intentkit/skills/twitter/base.py +68 -0
  290. intentkit/skills/twitter/follow_user.py +69 -0
  291. intentkit/skills/twitter/get_mentions.py +124 -0
  292. intentkit/skills/twitter/get_timeline.py +111 -0
  293. intentkit/skills/twitter/get_user_by_username.py +84 -0
  294. intentkit/skills/twitter/get_user_tweets.py +123 -0
  295. intentkit/skills/twitter/like_tweet.py +65 -0
  296. intentkit/skills/twitter/post_tweet.py +90 -0
  297. intentkit/skills/twitter/reply_tweet.py +98 -0
  298. intentkit/skills/twitter/retweet.py +76 -0
  299. intentkit/skills/twitter/schema.json +258 -0
  300. intentkit/skills/twitter/search_tweets.py +115 -0
  301. intentkit/skills/twitter/twitter.png +0 -0
  302. intentkit/skills/unrealspeech/__init__.py +55 -0
  303. intentkit/skills/unrealspeech/base.py +21 -0
  304. intentkit/skills/unrealspeech/schema.json +100 -0
  305. intentkit/skills/unrealspeech/text_to_speech.py +177 -0
  306. intentkit/skills/unrealspeech/unrealspeech.jpg +0 -0
  307. intentkit/skills/venice_audio/__init__.py +106 -0
  308. intentkit/skills/venice_audio/base.py +119 -0
  309. intentkit/skills/venice_audio/input.py +41 -0
  310. intentkit/skills/venice_audio/schema.json +152 -0
  311. intentkit/skills/venice_audio/venice_audio.py +240 -0
  312. intentkit/skills/venice_audio/venice_logo.jpg +0 -0
  313. intentkit/skills/venice_image/README.md +119 -0
  314. intentkit/skills/venice_image/__init__.py +154 -0
  315. intentkit/skills/venice_image/api.py +138 -0
  316. intentkit/skills/venice_image/base.py +188 -0
  317. intentkit/skills/venice_image/config.py +35 -0
  318. intentkit/skills/venice_image/image_enhance/README.md +119 -0
  319. intentkit/skills/venice_image/image_enhance/__init__.py +0 -0
  320. intentkit/skills/venice_image/image_enhance/image_enhance.py +80 -0
  321. intentkit/skills/venice_image/image_enhance/image_enhance_base.py +23 -0
  322. intentkit/skills/venice_image/image_enhance/image_enhance_input.py +40 -0
  323. intentkit/skills/venice_image/image_generation/README.md +144 -0
  324. intentkit/skills/venice_image/image_generation/__init__.py +0 -0
  325. intentkit/skills/venice_image/image_generation/image_generation_base.py +117 -0
  326. intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -0
  327. intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -0
  328. intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -0
  329. intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -0
  330. intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -0
  331. intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -0
  332. intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -0
  333. intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -0
  334. intentkit/skills/venice_image/image_upscale/README.md +111 -0
  335. intentkit/skills/venice_image/image_upscale/__init__.py +0 -0
  336. intentkit/skills/venice_image/image_upscale/image_upscale.py +90 -0
  337. intentkit/skills/venice_image/image_upscale/image_upscale_base.py +23 -0
  338. intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -0
  339. intentkit/skills/venice_image/image_vision/README.md +112 -0
  340. intentkit/skills/venice_image/image_vision/__init__.py +0 -0
  341. intentkit/skills/venice_image/image_vision/image_vision.py +100 -0
  342. intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -0
  343. intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -0
  344. intentkit/skills/venice_image/schema.json +267 -0
  345. intentkit/skills/venice_image/utils.py +78 -0
  346. intentkit/skills/venice_image/venice_image.jpg +0 -0
  347. intentkit/skills/web_scraper/README.md +82 -0
  348. intentkit/skills/web_scraper/__init__.py +92 -0
  349. intentkit/skills/web_scraper/base.py +21 -0
  350. intentkit/skills/web_scraper/langchain.png +0 -0
  351. intentkit/skills/web_scraper/schema.json +115 -0
  352. intentkit/skills/web_scraper/scrape_and_index.py +327 -0
  353. intentkit/utils/__init__.py +1 -0
  354. intentkit/utils/chain.py +436 -0
  355. intentkit/utils/error.py +134 -0
  356. intentkit/utils/logging.py +70 -0
  357. intentkit/utils/middleware.py +61 -0
  358. intentkit/utils/random.py +16 -0
  359. intentkit/utils/s3.py +267 -0
  360. intentkit/utils/slack_alert.py +79 -0
  361. intentkit/utils/tx.py +37 -0
  362. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/METADATA +1 -1
  363. intentkit-0.5.2.dist-info/RECORD +365 -0
  364. intentkit-0.5.0.dist-info/RECORD +0 -4
  365. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/WHEEL +0 -0
  366. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1689 @@
1
+ import json
2
+ import logging
3
+ import re
4
+ import textwrap
5
+ from datetime import datetime, timezone
6
+ from decimal import Decimal
7
+ from typing import Annotated, Any, Dict, List, Literal, Optional
8
+
9
+ import yaml
10
+ from cron_validator import CronValidator
11
+ from epyxid import XID
12
+ from fastapi import HTTPException
13
+ from intentkit.models.agent_data import AgentData
14
+ from intentkit.models.base import Base
15
+ from intentkit.models.db import get_session
16
+ from intentkit.models.llm import LLMModelInfo
17
+ from pydantic import BaseModel, ConfigDict, field_validator, model_validator
18
+ from pydantic import Field as PydanticField
19
+ from pydantic.json_schema import SkipJsonSchema
20
+ from sqlalchemy import (
21
+ BigInteger,
22
+ Boolean,
23
+ Column,
24
+ DateTime,
25
+ Float,
26
+ Identity,
27
+ Numeric,
28
+ String,
29
+ func,
30
+ select,
31
+ )
32
+ from sqlalchemy.dialects.postgresql import JSON, JSONB
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ class AgentAutonomous(BaseModel):
38
+ """Autonomous agent configuration."""
39
+
40
+ id: Annotated[
41
+ str,
42
+ PydanticField(
43
+ description="Unique identifier for the autonomous configuration",
44
+ default_factory=lambda: str(XID()),
45
+ min_length=1,
46
+ max_length=20,
47
+ pattern=r"^[a-z0-9-]+$",
48
+ json_schema_extra={
49
+ "x-group": "autonomous",
50
+ },
51
+ ),
52
+ ]
53
+ name: Annotated[
54
+ Optional[str],
55
+ PydanticField(
56
+ default=None,
57
+ description="Display name of the autonomous configuration",
58
+ max_length=50,
59
+ json_schema_extra={
60
+ "x-group": "autonomous",
61
+ },
62
+ ),
63
+ ]
64
+ description: Annotated[
65
+ Optional[str],
66
+ PydanticField(
67
+ default=None,
68
+ description="Description of the autonomous configuration",
69
+ max_length=200,
70
+ json_schema_extra={
71
+ "x-group": "autonomous",
72
+ },
73
+ ),
74
+ ]
75
+ minutes: Annotated[
76
+ Optional[int],
77
+ PydanticField(
78
+ default=None,
79
+ description="Interval in minutes between operations, mutually exclusive with cron",
80
+ json_schema_extra={
81
+ "x-group": "autonomous",
82
+ },
83
+ ),
84
+ ]
85
+ cron: Annotated[
86
+ Optional[str],
87
+ PydanticField(
88
+ default=None,
89
+ description="Cron expression for scheduling operations, mutually exclusive with minutes",
90
+ json_schema_extra={
91
+ "x-group": "autonomous",
92
+ },
93
+ ),
94
+ ]
95
+ prompt: Annotated[
96
+ str,
97
+ PydanticField(
98
+ description="Special prompt used during autonomous operation",
99
+ max_length=20000,
100
+ json_schema_extra={
101
+ "x-group": "autonomous",
102
+ },
103
+ ),
104
+ ]
105
+ enabled: Annotated[
106
+ Optional[bool],
107
+ PydanticField(
108
+ default=False,
109
+ description="Whether the autonomous configuration is enabled",
110
+ json_schema_extra={
111
+ "x-group": "autonomous",
112
+ },
113
+ ),
114
+ ]
115
+
116
+ @field_validator("id")
117
+ @classmethod
118
+ def validate_id(cls, v: str) -> str:
119
+ if not v:
120
+ raise ValueError("id cannot be empty")
121
+ if len(v.encode()) > 20:
122
+ raise ValueError("id must be at most 20 bytes")
123
+ if not re.match(r"^[a-z0-9-]+$", v):
124
+ raise ValueError(
125
+ "id must contain only lowercase letters, numbers, and dashes"
126
+ )
127
+ return v
128
+
129
+ @field_validator("name")
130
+ @classmethod
131
+ def validate_name(cls, v: Optional[str]) -> Optional[str]:
132
+ if v is not None and len(v.encode()) > 50:
133
+ raise ValueError("name must be at most 50 bytes")
134
+ return v
135
+
136
+ @field_validator("description")
137
+ @classmethod
138
+ def validate_description(cls, v: Optional[str]) -> Optional[str]:
139
+ if v is not None and len(v.encode()) > 200:
140
+ raise ValueError("description must be at most 200 bytes")
141
+ return v
142
+
143
+ @field_validator("prompt")
144
+ @classmethod
145
+ def validate_prompt(cls, v: Optional[str]) -> Optional[str]:
146
+ if v is not None and len(v.encode()) > 20000:
147
+ raise ValueError("prompt must be at most 20000 bytes")
148
+ return v
149
+
150
+ @model_validator(mode="after")
151
+ def validate_schedule(self) -> "AgentAutonomous":
152
+ # This validator is kept for backward compatibility
153
+ # The actual validation now happens in AgentUpdate.validate_autonomous_schedule
154
+ return self
155
+
156
+
157
+ class AgentExample(BaseModel):
158
+ """Agent example configuration."""
159
+
160
+ name: Annotated[
161
+ str,
162
+ PydanticField(
163
+ description="Name of the example",
164
+ max_length=50,
165
+ json_schema_extra={
166
+ "x-group": "examples",
167
+ },
168
+ ),
169
+ ]
170
+ description: Annotated[
171
+ str,
172
+ PydanticField(
173
+ description="Description of the example",
174
+ max_length=200,
175
+ json_schema_extra={
176
+ "x-group": "examples",
177
+ },
178
+ ),
179
+ ]
180
+ prompt: Annotated[
181
+ str,
182
+ PydanticField(
183
+ description="Example prompt",
184
+ max_length=2000,
185
+ json_schema_extra={
186
+ "x-group": "examples",
187
+ },
188
+ ),
189
+ ]
190
+
191
+
192
+ class AgentTable(Base):
193
+ """Agent table db model."""
194
+
195
+ __tablename__ = "agents"
196
+
197
+ id = Column(
198
+ String,
199
+ primary_key=True,
200
+ comment="Unique identifier for the agent. Must be URL-safe, containing only lowercase letters, numbers, and hyphens",
201
+ )
202
+ number = Column(
203
+ BigInteger(),
204
+ Identity(start=1, increment=1),
205
+ nullable=True,
206
+ comment="Auto-incrementing number assigned by the system for easy reference",
207
+ )
208
+ name = Column(
209
+ String,
210
+ nullable=True,
211
+ comment="Display name of the agent",
212
+ )
213
+ slug = Column(
214
+ String,
215
+ nullable=True,
216
+ comment="Slug of the agent, used for URL generation",
217
+ )
218
+ description = Column(
219
+ String,
220
+ nullable=True,
221
+ comment="Description of the agent, for public view, not contained in prompt",
222
+ )
223
+ external_website = Column(
224
+ String,
225
+ nullable=True,
226
+ comment="Link of external website of the agent, if you have one",
227
+ )
228
+ picture = Column(
229
+ String,
230
+ nullable=True,
231
+ comment="Picture of the agent",
232
+ )
233
+ ticker = Column(
234
+ String,
235
+ nullable=True,
236
+ comment="Ticker symbol of the agent",
237
+ )
238
+ token_address = Column(
239
+ String,
240
+ nullable=True,
241
+ comment="Token address of the agent",
242
+ )
243
+ token_pool = Column(
244
+ String,
245
+ nullable=True,
246
+ comment="Pool of the agent token",
247
+ )
248
+ mode = Column(
249
+ String,
250
+ nullable=True,
251
+ comment="Mode of the agent, public or private",
252
+ )
253
+ fee_percentage = Column(
254
+ Numeric(22, 4),
255
+ nullable=True,
256
+ comment="Fee percentage of the agent",
257
+ )
258
+ purpose = Column(
259
+ String,
260
+ nullable=True,
261
+ comment="Purpose or role of the agent",
262
+ )
263
+ personality = Column(
264
+ String,
265
+ nullable=True,
266
+ comment="Personality traits of the agent",
267
+ )
268
+ principles = Column(
269
+ String,
270
+ nullable=True,
271
+ comment="Principles or values of the agent",
272
+ )
273
+ owner = Column(
274
+ String,
275
+ nullable=True,
276
+ comment="Owner identifier of the agent, used for access control",
277
+ )
278
+ upstream_id = Column(
279
+ String,
280
+ index=True,
281
+ nullable=True,
282
+ comment="Upstream reference ID for idempotent operations",
283
+ )
284
+ upstream_extra = Column(
285
+ JSON().with_variant(JSONB(), "postgresql"),
286
+ nullable=True,
287
+ comment="Additional data store for upstream use",
288
+ )
289
+ wallet_provider = Column(
290
+ String,
291
+ nullable=True,
292
+ comment="Provider of the agent's wallet",
293
+ )
294
+ network_id = Column(
295
+ String,
296
+ nullable=True,
297
+ default="base-mainnet",
298
+ comment="Network identifier",
299
+ )
300
+ # AI part
301
+ model = Column(
302
+ String,
303
+ nullable=True,
304
+ default="gpt-4.1-mini",
305
+ comment="AI model identifier to be used by this agent for processing requests. Available models: gpt-4o, gpt-4o-mini, deepseek-chat, deepseek-reasoner, grok-2, eternalai",
306
+ )
307
+ prompt = Column(
308
+ String,
309
+ nullable=True,
310
+ comment="Base system prompt that defines the agent's behavior and capabilities",
311
+ )
312
+ prompt_append = Column(
313
+ String,
314
+ nullable=True,
315
+ comment="Additional system prompt that has higher priority than the base prompt",
316
+ )
317
+ temperature = Column(
318
+ Float,
319
+ nullable=True,
320
+ default=0.7,
321
+ comment="Controls response randomness (0.0~2.0). Higher values increase creativity but may reduce accuracy. For rigorous tasks, use lower values.",
322
+ )
323
+ frequency_penalty = Column(
324
+ Float,
325
+ nullable=True,
326
+ default=0.0,
327
+ comment="Controls repetition in responses (-2.0~2.0). Higher values reduce repetition, lower values allow more repetition.",
328
+ )
329
+ presence_penalty = Column(
330
+ Float,
331
+ nullable=True,
332
+ default=0.0,
333
+ comment="Controls topic adherence (-2.0~2.0). Higher values allow more topic deviation, lower values enforce stricter topic adherence.",
334
+ )
335
+ short_term_memory_strategy = Column(
336
+ String,
337
+ nullable=True,
338
+ default="trim",
339
+ comment="Strategy for managing short-term memory when context limit is reached. 'trim' removes oldest messages, 'summarize' creates summaries.",
340
+ )
341
+ # autonomous mode
342
+ autonomous = Column(
343
+ JSON().with_variant(JSONB(), "postgresql"),
344
+ nullable=True,
345
+ comment="Autonomous agent configurations",
346
+ )
347
+ # agent examples
348
+ example_intro = Column(
349
+ String,
350
+ nullable=True,
351
+ comment="Introduction for example interactions",
352
+ )
353
+ examples = Column(
354
+ JSON().with_variant(JSONB(), "postgresql"),
355
+ nullable=True,
356
+ comment="List of example interactions for the agent",
357
+ )
358
+ # skills
359
+ skills = Column(
360
+ JSON().with_variant(JSONB(), "postgresql"),
361
+ nullable=True,
362
+ comment="Dict of skills and their corresponding configurations",
363
+ )
364
+
365
+ cdp_network_id = Column(
366
+ String,
367
+ nullable=True,
368
+ default="base-mainnet",
369
+ comment="Network identifier for CDP integration",
370
+ )
371
+ # if twitter_enabled, the twitter_entrypoint will be enabled, twitter_config will be checked
372
+ twitter_entrypoint_enabled = Column(
373
+ Boolean,
374
+ nullable=True,
375
+ default=False,
376
+ comment="Dangerous, reply all mentions from x.com",
377
+ )
378
+ twitter_entrypoint_prompt = Column(
379
+ String,
380
+ nullable=True,
381
+ comment="Extra prompt for twitter entrypoint",
382
+ )
383
+ twitter_config = Column(
384
+ JSON().with_variant(JSONB(), "postgresql"),
385
+ nullable=True,
386
+ comment="You must use your own key for twitter entrypoint, it is separated from twitter skills",
387
+ )
388
+ # if telegram_entrypoint_enabled, the telegram_entrypoint_enabled will be enabled, telegram_config will be checked
389
+ telegram_entrypoint_enabled = Column(
390
+ Boolean,
391
+ nullable=True,
392
+ default=False,
393
+ comment="Whether the agent can receive events from Telegram",
394
+ )
395
+ telegram_entrypoint_prompt = Column(
396
+ String,
397
+ nullable=True,
398
+ comment="Extra prompt for telegram entrypoint",
399
+ )
400
+ telegram_config = Column(
401
+ JSON().with_variant(JSONB(), "postgresql"),
402
+ nullable=True,
403
+ comment="Telegram integration configuration settings",
404
+ )
405
+ # auto timestamp
406
+ created_at = Column(
407
+ DateTime(timezone=True),
408
+ nullable=False,
409
+ server_default=func.now(),
410
+ comment="Timestamp when the agent was created",
411
+ )
412
+ updated_at = Column(
413
+ DateTime(timezone=True),
414
+ nullable=False,
415
+ server_default=func.now(),
416
+ onupdate=lambda: datetime.now(timezone.utc),
417
+ comment="Timestamp when the agent was last updated",
418
+ )
419
+
420
+
421
+ class AgentUpdate(BaseModel):
422
+ """Agent update model."""
423
+
424
+ model_config = ConfigDict(
425
+ title="Agent",
426
+ from_attributes=True,
427
+ json_schema_extra={
428
+ "required": ["name", "purpose", "personality", "principles"],
429
+ },
430
+ )
431
+
432
+ name: Annotated[
433
+ Optional[str],
434
+ PydanticField(
435
+ default=None,
436
+ title="Name",
437
+ description="Display name of the agent",
438
+ max_length=50,
439
+ json_schema_extra={
440
+ "x-group": "basic",
441
+ "x-placeholder": "Name your agent",
442
+ },
443
+ ),
444
+ ]
445
+ slug: Annotated[
446
+ Optional[str],
447
+ PydanticField(
448
+ default=None,
449
+ description="Slug of the agent, used for URL generation",
450
+ max_length=30,
451
+ min_length=2,
452
+ json_schema_extra={
453
+ "x-group": "internal",
454
+ "readOnly": True,
455
+ },
456
+ ),
457
+ ]
458
+ description: Annotated[
459
+ Optional[str],
460
+ PydanticField(
461
+ default=None,
462
+ description="Description of the agent, for public view, not contained in prompt",
463
+ json_schema_extra={
464
+ "x-group": "basic",
465
+ "x-placeholder": "Introduce your agent",
466
+ },
467
+ ),
468
+ ]
469
+ external_website: Annotated[
470
+ Optional[str],
471
+ PydanticField(
472
+ default=None,
473
+ description="Link of external website of the agent, if you have one",
474
+ json_schema_extra={
475
+ "x-group": "basic",
476
+ "x-placeholder": "Enter agent external website url",
477
+ "format": "uri",
478
+ },
479
+ ),
480
+ ]
481
+ picture: Annotated[
482
+ Optional[str],
483
+ PydanticField(
484
+ default=None,
485
+ description="Picture of the agent",
486
+ json_schema_extra={
487
+ "x-group": "experimental",
488
+ "x-placeholder": "Upload a picture of your agent",
489
+ },
490
+ ),
491
+ ]
492
+ ticker: Annotated[
493
+ Optional[str],
494
+ PydanticField(
495
+ default=None,
496
+ description="Ticker symbol of the agent",
497
+ max_length=10,
498
+ min_length=1,
499
+ json_schema_extra={
500
+ "x-group": "basic",
501
+ "x-placeholder": "If one day, your agent has it's own token, what will it be?",
502
+ },
503
+ ),
504
+ ]
505
+ token_address: Annotated[
506
+ Optional[str],
507
+ PydanticField(
508
+ default=None,
509
+ description="Token address of the agent",
510
+ max_length=42,
511
+ json_schema_extra={
512
+ "x-group": "internal",
513
+ "readOnly": True,
514
+ },
515
+ ),
516
+ ]
517
+ token_pool: Annotated[
518
+ Optional[str],
519
+ PydanticField(
520
+ default=None,
521
+ description="Pool of the agent token",
522
+ max_length=42,
523
+ json_schema_extra={
524
+ "x-group": "internal",
525
+ "readOnly": True,
526
+ },
527
+ ),
528
+ ]
529
+ mode: Annotated[
530
+ Optional[Literal["public", "private"]],
531
+ PydanticField(
532
+ default=None,
533
+ description="Mode of the agent, public or private",
534
+ json_schema_extra={
535
+ "x-group": "basic",
536
+ },
537
+ ),
538
+ ]
539
+ fee_percentage: Annotated[
540
+ Optional[Decimal],
541
+ PydanticField(
542
+ default=None,
543
+ description="Fee percentage of the agent",
544
+ ge=Decimal("0.0"),
545
+ json_schema_extra={
546
+ "x-group": "basic",
547
+ },
548
+ ),
549
+ ]
550
+ purpose: Annotated[
551
+ Optional[str],
552
+ PydanticField(
553
+ default=None,
554
+ description="Purpose or role of the agent",
555
+ max_length=20000,
556
+ json_schema_extra={
557
+ "x-group": "basic",
558
+ "x-placeholder": "Enter agent purpose, it will be a part of the system prompt",
559
+ "pattern": "^(([^#].*)|#[^# ].*|#{3,}[ ].*|$)(\n(([^#].*)|#[^# ].*|#{3,}[ ].*|$))*$",
560
+ "errorMessage": {
561
+ "pattern": "Level 1 and 2 headings (# and ##) are not allowed. Please use level 3+ headings (###, ####, etc.) instead."
562
+ },
563
+ },
564
+ ),
565
+ ]
566
+ personality: Annotated[
567
+ Optional[str],
568
+ PydanticField(
569
+ default=None,
570
+ description="Personality traits of the agent",
571
+ max_length=20000,
572
+ json_schema_extra={
573
+ "x-group": "basic",
574
+ "x-placeholder": "Enter agent personality, it will be a part of the system prompt",
575
+ "pattern": "^(([^#].*)|#[^# ].*|#{3,}[ ].*|$)(\n(([^#].*)|#[^# ].*|#{3,}[ ].*|$))*$",
576
+ "errorMessage": {
577
+ "pattern": "Level 1 and 2 headings (# and ##) are not allowed. Please use level 3+ headings (###, ####, etc.) instead."
578
+ },
579
+ },
580
+ ),
581
+ ]
582
+ principles: Annotated[
583
+ Optional[str],
584
+ PydanticField(
585
+ default=None,
586
+ description="Principles or values of the agent",
587
+ max_length=20000,
588
+ json_schema_extra={
589
+ "x-group": "basic",
590
+ "x-placeholder": "Enter agent principles, it will be a part of the system prompt",
591
+ "pattern": "^(([^#].*)|#[^# ].*|#{3,}[ ].*|$)(\n(([^#].*)|#[^# ].*|#{3,}[ ].*|$))*$",
592
+ "errorMessage": {
593
+ "pattern": "Level 1 and 2 headings (# and ##) are not allowed. Please use level 3+ headings (###, ####, etc.) instead."
594
+ },
595
+ },
596
+ ),
597
+ ]
598
+ owner: Annotated[
599
+ Optional[str],
600
+ PydanticField(
601
+ default=None,
602
+ description="Owner identifier of the agent, used for access control",
603
+ max_length=50,
604
+ json_schema_extra={
605
+ "x-group": "internal",
606
+ },
607
+ ),
608
+ ]
609
+ upstream_id: Annotated[
610
+ Optional[str],
611
+ PydanticField(
612
+ default=None,
613
+ description="External reference ID for idempotent operations",
614
+ max_length=100,
615
+ json_schema_extra={
616
+ "x-group": "internal",
617
+ },
618
+ ),
619
+ ]
620
+ upstream_extra: Annotated[
621
+ Optional[Dict[str, Any]],
622
+ PydanticField(
623
+ default=None,
624
+ description="Additional data store for upstream use",
625
+ json_schema_extra={
626
+ "x-group": "internal",
627
+ },
628
+ ),
629
+ ]
630
+ # AI part
631
+ model: Annotated[
632
+ str,
633
+ PydanticField(
634
+ default="gpt-4.1-mini",
635
+ description="AI model identifier to be used by this agent for processing requests. Available models: gpt-4o, gpt-4o-mini, deepseek-chat, deepseek-reasoner, grok-2, eternalai, reigent, venice-uncensored",
636
+ json_schema_extra={
637
+ "x-group": "ai",
638
+ },
639
+ ),
640
+ ]
641
+ prompt: Annotated[
642
+ Optional[str],
643
+ PydanticField(
644
+ default=None,
645
+ description="Base system prompt that defines the agent's behavior and capabilities",
646
+ max_length=20000,
647
+ json_schema_extra={
648
+ "x-group": "ai",
649
+ "pattern": "^(([^#].*)|#[^# ].*|#{3,}[ ].*|$)(\n(([^#].*)|#[^# ].*|#{3,}[ ].*|$))*$",
650
+ "errorMessage": {
651
+ "pattern": "Level 1 and 2 headings (# and ##) are not allowed. Please use level 3+ headings (###, ####, etc.) instead."
652
+ },
653
+ },
654
+ ),
655
+ ]
656
+ prompt_append: Annotated[
657
+ Optional[str],
658
+ PydanticField(
659
+ default=None,
660
+ description="Additional system prompt that has higher priority than the base prompt",
661
+ max_length=20000,
662
+ json_schema_extra={
663
+ "x-group": "ai",
664
+ "pattern": "^(([^#].*)|#[^# ].*|#{3,}[ ].*|$)(\n(([^#].*)|#[^# ].*|#{3,}[ ].*|$))*$",
665
+ "errorMessage": {
666
+ "pattern": "Level 1 and 2 headings (# and ##) are not allowed. Please use level 3+ headings (###, ####, etc.) instead."
667
+ },
668
+ },
669
+ ),
670
+ ]
671
+ temperature: Annotated[
672
+ Optional[float],
673
+ PydanticField(
674
+ default=0.7,
675
+ description="The randomness of the generated results is such that the higher the number, the more creative the results will be. However, this also makes them wilder and increases the likelihood of errors. For creative tasks, you can adjust it to above 1, but for rigorous tasks, such as quantitative trading, it's advisable to set it lower, around 0.2. (0.0~2.0)",
676
+ ge=0.0,
677
+ le=2.0,
678
+ json_schema_extra={
679
+ "x-group": "ai",
680
+ },
681
+ ),
682
+ ]
683
+ frequency_penalty: Annotated[
684
+ Optional[float],
685
+ PydanticField(
686
+ default=0.0,
687
+ description="The frequency penalty is a measure of how much the AI is allowed to repeat itself. A lower value means the AI is more likely to repeat previous responses, while a higher value means the AI is more likely to generate new content. For creative tasks, you can adjust it to 1 or a bit higher. (-2.0~2.0)",
688
+ ge=-2.0,
689
+ le=2.0,
690
+ json_schema_extra={
691
+ "x-group": "ai",
692
+ },
693
+ ),
694
+ ]
695
+ presence_penalty: Annotated[
696
+ Optional[float],
697
+ PydanticField(
698
+ default=0.0,
699
+ description="The presence penalty is a measure of how much the AI is allowed to deviate from the topic. A higher value means the AI is more likely to deviate from the topic, while a lower value means the AI is more likely to follow the topic. For creative tasks, you can adjust it to 1 or a bit higher. (-2.0~2.0)",
700
+ ge=-2.0,
701
+ le=2.0,
702
+ json_schema_extra={
703
+ "x-group": "ai",
704
+ },
705
+ ),
706
+ ]
707
+ short_term_memory_strategy: Annotated[
708
+ Optional[Literal["trim", "summarize"]],
709
+ PydanticField(
710
+ default="trim",
711
+ description="Strategy for managing short-term memory when context limit is reached. 'trim' removes oldest messages, 'summarize' creates summaries.",
712
+ json_schema_extra={
713
+ "x-group": "ai",
714
+ },
715
+ ),
716
+ ]
717
+ # autonomous mode
718
+ autonomous: Annotated[
719
+ Optional[List[AgentAutonomous]],
720
+ PydanticField(
721
+ default=None,
722
+ description=(
723
+ "Autonomous agent configurations.\n"
724
+ "autonomous:\n"
725
+ " - id: a\n"
726
+ " name: TestA\n"
727
+ " minutes: 1\n"
728
+ " prompt: |-\n"
729
+ " Say hello [sequence], use number for sequence.\n"
730
+ " - id: b\n"
731
+ " name: TestB\n"
732
+ ' cron: "0/3 * * * *"\n'
733
+ " prompt: |-\n"
734
+ " Say hi [sequence], use number for sequence.\n"
735
+ ),
736
+ json_schema_extra={
737
+ "x-group": "autonomous",
738
+ "x-inline": True,
739
+ },
740
+ ),
741
+ ]
742
+ example_intro: Annotated[
743
+ Optional[str],
744
+ PydanticField(
745
+ default=None,
746
+ description="Introduction of the example",
747
+ max_length=2000,
748
+ json_schema_extra={
749
+ "x-group": "examples",
750
+ },
751
+ ),
752
+ ]
753
+ examples: Annotated[
754
+ Optional[List[AgentExample]],
755
+ PydanticField(
756
+ default=None,
757
+ description="List of example prompts for the agent",
758
+ max_length=6,
759
+ json_schema_extra={
760
+ "x-group": "examples",
761
+ "x-inline": True,
762
+ },
763
+ ),
764
+ ]
765
+ # skills
766
+ skills: Annotated[
767
+ Optional[Dict[str, Any]],
768
+ PydanticField(
769
+ default=None,
770
+ description="Dict of skills and their corresponding configurations",
771
+ json_schema_extra={
772
+ "x-group": "skills",
773
+ "x-inline": True,
774
+ },
775
+ ),
776
+ ]
777
+ wallet_provider: Annotated[
778
+ Optional[Literal["cdp"]],
779
+ PydanticField(
780
+ default="cdp",
781
+ description="Provider of the agent's wallet",
782
+ json_schema_extra={
783
+ "x-group": "onchain",
784
+ },
785
+ ),
786
+ ]
787
+ network_id: Annotated[
788
+ Optional[
789
+ Literal[
790
+ "ethereum-mainnet",
791
+ "ethereum-sepolia",
792
+ "polygon-mainnet",
793
+ "polygon-mumbai",
794
+ "base-mainnet",
795
+ "base-sepolia",
796
+ "arbitrum-mainnet",
797
+ "arbitrum-sepolia",
798
+ "optimism-mainnet",
799
+ "optimism-sepolia",
800
+ "solana",
801
+ ]
802
+ ],
803
+ PydanticField(
804
+ default="base-mainnet",
805
+ description="Network identifier",
806
+ json_schema_extra={
807
+ "x-group": "onchain",
808
+ },
809
+ ),
810
+ ]
811
+ cdp_network_id: Annotated[
812
+ Optional[
813
+ Literal[
814
+ "ethereum-mainnet",
815
+ "ethereum-sepolia",
816
+ "polygon-mainnet",
817
+ "polygon-mumbai",
818
+ "base-mainnet",
819
+ "base-sepolia",
820
+ "arbitrum-mainnet",
821
+ "arbitrum-sepolia",
822
+ "optimism-mainnet",
823
+ "optimism-sepolia",
824
+ ]
825
+ ],
826
+ PydanticField(
827
+ default="base-mainnet",
828
+ description="Network identifier for CDP integration",
829
+ json_schema_extra={
830
+ "x-group": "deprecated",
831
+ },
832
+ ),
833
+ ]
834
+ # if twitter_enabled, the twitter_entrypoint will be enabled, twitter_config will be checked
835
+ twitter_entrypoint_enabled: Annotated[
836
+ Optional[bool],
837
+ PydanticField(
838
+ default=False,
839
+ description="Dangerous, reply all mentions from x.com",
840
+ json_schema_extra={
841
+ "x-group": "entrypoint",
842
+ },
843
+ ),
844
+ ]
845
+ twitter_entrypoint_prompt: Annotated[
846
+ Optional[str],
847
+ PydanticField(
848
+ default=None,
849
+ description="Extra prompt for twitter entrypoint",
850
+ max_length=10000,
851
+ json_schema_extra={
852
+ "x-group": "entrypoint",
853
+ },
854
+ ),
855
+ ]
856
+ twitter_config: Annotated[
857
+ Optional[dict],
858
+ PydanticField(
859
+ default=None,
860
+ description="You must use your own key for twitter entrypoint, it is separated from twitter skills",
861
+ json_schema_extra={
862
+ "x-group": "entrypoint",
863
+ },
864
+ ),
865
+ ]
866
+ # if telegram_entrypoint_enabled, the telegram_entrypoint_enabled will be enabled, telegram_config will be checked
867
+ telegram_entrypoint_enabled: Annotated[
868
+ Optional[bool],
869
+ PydanticField(
870
+ default=False,
871
+ description="Whether the agent can play telegram bot",
872
+ json_schema_extra={
873
+ "x-group": "entrypoint",
874
+ },
875
+ ),
876
+ ]
877
+ telegram_entrypoint_prompt: Annotated[
878
+ Optional[str],
879
+ PydanticField(
880
+ default=None,
881
+ description="Extra prompt for telegram entrypoint",
882
+ max_length=10000,
883
+ json_schema_extra={
884
+ "x-group": "entrypoint",
885
+ },
886
+ ),
887
+ ]
888
+ telegram_config: Annotated[
889
+ Optional[dict],
890
+ PydanticField(
891
+ default=None,
892
+ description="Telegram integration configuration settings",
893
+ json_schema_extra={
894
+ "x-group": "entrypoint",
895
+ },
896
+ ),
897
+ ]
898
+
899
+ @field_validator("purpose", "personality", "principles", "prompt", "prompt_append")
900
+ @classmethod
901
+ def validate_no_level1_level2_headings(cls, v: Optional[str]) -> Optional[str]:
902
+ """Validate that the text doesn't contain level 1 or level 2 headings."""
903
+ if v is None:
904
+ return v
905
+
906
+ import re
907
+
908
+ # Check if any line starts with # or ## followed by a space
909
+ if re.search(r"^(# |## )", v, re.MULTILINE):
910
+ raise ValueError(
911
+ "Level 1 and 2 headings (# and ##) are not allowed. Please use level 3+ headings (###, ####, etc.) instead."
912
+ )
913
+ return v
914
+
915
+ def validate_autonomous_schedule(self) -> None:
916
+ """Validate the schedule settings for autonomous configurations.
917
+
918
+ This validation ensures:
919
+ 1. Only one scheduling method (minutes or cron) is set per autonomous config
920
+ 2. The minimum interval is 5 minutes for both types of schedules
921
+ """
922
+ if not self.autonomous:
923
+ return
924
+
925
+ for config in self.autonomous:
926
+ # Check that exactly one scheduling method is provided
927
+ if not config.minutes and not config.cron:
928
+ raise HTTPException(
929
+ status_code=400, detail="either minutes or cron must have a value"
930
+ )
931
+
932
+ if config.minutes and config.cron:
933
+ raise HTTPException(
934
+ status_code=400, detail="only one of minutes or cron can be set"
935
+ )
936
+
937
+ # Validate minimum interval of 5 minutes
938
+ if config.minutes and config.minutes < 5:
939
+ raise HTTPException(
940
+ status_code=400,
941
+ detail="The shortest execution interval is 5 minutes",
942
+ )
943
+
944
+ # Validate cron expression to ensure interval is at least 5 minutes
945
+ if config.cron:
946
+ # First validate the cron expression format using cron-validator
947
+
948
+ try:
949
+ CronValidator.parse(config.cron)
950
+ except ValueError:
951
+ raise HTTPException(
952
+ status_code=400,
953
+ detail=f"Invalid cron expression format: {config.cron}",
954
+ )
955
+
956
+ parts = config.cron.split()
957
+ if len(parts) < 5:
958
+ raise HTTPException(
959
+ status_code=400, detail="Invalid cron expression format"
960
+ )
961
+
962
+ minute, hour, day_of_month, month, day_of_week = parts[:5]
963
+
964
+ # Check if minutes or hours have too frequent intervals
965
+ if "*" in minute and "*" in hour:
966
+ # If both minute and hour are wildcards, it would run every minute
967
+ raise HTTPException(
968
+ status_code=400,
969
+ detail="The shortest execution interval is 5 minutes",
970
+ )
971
+
972
+ if "/" in minute:
973
+ # Check step value in minute field (e.g., */15)
974
+ step = int(minute.split("/")[1])
975
+ if step < 5 and hour == "*":
976
+ raise HTTPException(
977
+ status_code=400,
978
+ detail="The shortest execution interval is 5 minutes",
979
+ )
980
+
981
+ # Check for comma-separated values or ranges that might result in multiple executions per hour
982
+ if ("," in minute or "-" in minute) and hour == "*":
983
+ raise HTTPException(
984
+ status_code=400,
985
+ detail="The shortest execution interval is 5 minutes",
986
+ )
987
+
988
+ async def update(self, id: str) -> "Agent":
989
+ # Validate autonomous schedule settings if present
990
+ if "autonomous" in self.model_dump(exclude_unset=True):
991
+ self.validate_autonomous_schedule()
992
+
993
+ async with get_session() as db:
994
+ db_agent = await db.get(AgentTable, id)
995
+ if not db_agent:
996
+ raise HTTPException(status_code=404, detail="Agent not found")
997
+ # check owner
998
+ if self.owner and db_agent.owner != self.owner:
999
+ raise HTTPException(
1000
+ status_code=403,
1001
+ detail="You do not have permission to update this agent",
1002
+ )
1003
+ # update
1004
+ for key, value in self.model_dump(exclude_unset=True).items():
1005
+ setattr(db_agent, key, value)
1006
+ await db.commit()
1007
+ await db.refresh(db_agent)
1008
+ return Agent.model_validate(db_agent)
1009
+
1010
+ async def override(self, id: str) -> "Agent":
1011
+ # Validate autonomous schedule settings if present
1012
+ if "autonomous" in self.model_dump(exclude_unset=True):
1013
+ self.validate_autonomous_schedule()
1014
+
1015
+ async with get_session() as db:
1016
+ db_agent = await db.get(AgentTable, id)
1017
+ if not db_agent:
1018
+ raise HTTPException(status_code=404, detail="Agent not found")
1019
+ # check owner
1020
+ if db_agent.owner and db_agent.owner != self.owner:
1021
+ raise HTTPException(
1022
+ status_code=403,
1023
+ detail="You do not have permission to update this agent",
1024
+ )
1025
+ # update
1026
+ for key, value in self.model_dump().items():
1027
+ setattr(db_agent, key, value)
1028
+ await db.commit()
1029
+ await db.refresh(db_agent)
1030
+ return Agent.model_validate(db_agent)
1031
+
1032
+
1033
+ class AgentCreate(AgentUpdate):
1034
+ """Agent create model."""
1035
+
1036
+ id: Annotated[
1037
+ str,
1038
+ PydanticField(
1039
+ default_factory=lambda: str(XID()),
1040
+ description="Unique identifier for the agent. Must be URL-safe, containing only lowercase letters, numbers, and hyphens",
1041
+ pattern=r"^[a-z][a-z0-9-]*$",
1042
+ min_length=2,
1043
+ max_length=67,
1044
+ ),
1045
+ ]
1046
+
1047
+ async def check_upstream_id(self) -> None:
1048
+ if not self.upstream_id:
1049
+ return None
1050
+ async with get_session() as db:
1051
+ existing = await db.scalar(
1052
+ select(AgentTable).where(AgentTable.upstream_id == self.upstream_id)
1053
+ )
1054
+ if existing:
1055
+ raise HTTPException(
1056
+ status_code=400,
1057
+ detail="Upstream id already in use",
1058
+ )
1059
+
1060
+ async def get_by_upstream_id(self) -> Optional["Agent"]:
1061
+ if not self.upstream_id:
1062
+ return None
1063
+ async with get_session() as db:
1064
+ existing = await db.scalar(
1065
+ select(AgentTable).where(AgentTable.upstream_id == self.upstream_id)
1066
+ )
1067
+ if existing:
1068
+ return Agent.model_validate(existing)
1069
+ return None
1070
+
1071
+ async def create(self) -> "Agent":
1072
+ # Validate autonomous schedule settings if present
1073
+ if self.autonomous:
1074
+ self.validate_autonomous_schedule()
1075
+
1076
+ async with get_session() as db:
1077
+ db_agent = AgentTable(**self.model_dump())
1078
+ db.add(db_agent)
1079
+ await db.commit()
1080
+ await db.refresh(db_agent)
1081
+ return Agent.model_validate(db_agent)
1082
+
1083
+ async def create_or_update(self) -> ("Agent", bool):
1084
+ # Validation is now handled by field validators
1085
+ await self.check_upstream_id()
1086
+
1087
+ # Validate autonomous schedule settings if present
1088
+ if self.autonomous:
1089
+ self.validate_autonomous_schedule()
1090
+
1091
+ is_new = False
1092
+ async with get_session() as db:
1093
+ db_agent = await db.get(AgentTable, self.id)
1094
+ if not db_agent:
1095
+ db_agent = AgentTable(**self.model_dump())
1096
+ db.add(db_agent)
1097
+ is_new = True
1098
+ else:
1099
+ # check owner
1100
+ if self.owner and db_agent.owner != self.owner:
1101
+ raise HTTPException(
1102
+ status_code=403,
1103
+ detail="You do not have permission to update this agent",
1104
+ )
1105
+ for key, value in self.model_dump(exclude_unset=True).items():
1106
+ setattr(db_agent, key, value)
1107
+ await db.commit()
1108
+ await db.refresh(db_agent)
1109
+ return Agent.model_validate(db_agent), is_new
1110
+
1111
+
1112
+ class Agent(AgentCreate):
1113
+ """Agent model."""
1114
+
1115
+ model_config = ConfigDict(from_attributes=True)
1116
+
1117
+ # auto increment number by db
1118
+ number: Annotated[
1119
+ Optional[int],
1120
+ PydanticField(
1121
+ description="Auto-incrementing number assigned by the system for easy reference",
1122
+ ),
1123
+ ]
1124
+ # auto timestamp
1125
+ created_at: Annotated[
1126
+ datetime,
1127
+ PydanticField(
1128
+ description="Timestamp when the agent was created, will ignore when importing"
1129
+ ),
1130
+ ]
1131
+ updated_at: Annotated[
1132
+ datetime,
1133
+ PydanticField(
1134
+ description="Timestamp when the agent was last updated, will ignore when importing"
1135
+ ),
1136
+ ]
1137
+
1138
+ def has_image_parser_skill(self, is_private: bool = False) -> bool:
1139
+ if self.skills:
1140
+ for skill, skill_config in self.skills.items():
1141
+ if skill == "openai" and skill_config.get("enabled"):
1142
+ states = skill_config.get("states", {})
1143
+ if is_private:
1144
+ # Include both private and public when is_private=True
1145
+ if states.get("image_to_text") in ["private", "public"]:
1146
+ return True
1147
+ if states.get("gpt_image_to_image") in ["private", "public"]:
1148
+ return True
1149
+ else:
1150
+ # Only public when is_private=False
1151
+ if states.get("image_to_text") in ["public"]:
1152
+ return True
1153
+ if states.get("gpt_image_to_image") in ["public"]:
1154
+ return True
1155
+ return False
1156
+
1157
+ async def is_model_support_image(self) -> bool:
1158
+ model = await LLMModelInfo.get(self.model)
1159
+ return model.supports_image_input
1160
+
1161
+ def to_yaml(self) -> str:
1162
+ """
1163
+ Dump the agent model to YAML format with field descriptions as comments.
1164
+ The comments are extracted from the field descriptions in the model.
1165
+ Fields annotated with SkipJsonSchema will be excluded from the output.
1166
+ Only fields from AgentUpdate model are included.
1167
+ Deprecated fields with None or empty values are skipped.
1168
+
1169
+ Returns:
1170
+ str: YAML representation of the agent with field descriptions as comments
1171
+ """
1172
+ data = {}
1173
+ yaml_lines = []
1174
+
1175
+ def wrap_text(text: str, width: int = 80, prefix: str = "# ") -> list[str]:
1176
+ """Wrap text to specified width, preserving existing line breaks."""
1177
+ lines = []
1178
+ for paragraph in text.split("\n"):
1179
+ if not paragraph:
1180
+ lines.append(prefix.rstrip())
1181
+ continue
1182
+ # Use textwrap to wrap each paragraph
1183
+ wrapped = textwrap.wrap(paragraph, width=width - len(prefix))
1184
+ lines.extend(prefix + line for line in wrapped)
1185
+ return lines
1186
+
1187
+ # Get the field names from AgentUpdate model for filtering
1188
+ agent_update_fields = set(AgentUpdate.model_fields.keys())
1189
+
1190
+ for field_name, field in self.model_fields.items():
1191
+ logger.debug(f"Processing field {field_name} with type {field.metadata}")
1192
+ # Skip fields that are not in AgentUpdate model
1193
+ if field_name not in agent_update_fields:
1194
+ continue
1195
+
1196
+ # Skip fields with SkipJsonSchema annotation
1197
+ if any(isinstance(item, SkipJsonSchema) for item in field.metadata):
1198
+ continue
1199
+
1200
+ value = getattr(self, field_name)
1201
+
1202
+ # Skip deprecated fields with None or empty values
1203
+ is_deprecated = hasattr(field, "deprecated") and field.deprecated
1204
+ if is_deprecated and not value:
1205
+ continue
1206
+
1207
+ data[field_name] = value
1208
+ # Add comment from field description if available
1209
+ description = field.description
1210
+ if description:
1211
+ if len(yaml_lines) > 0: # Add blank line between fields
1212
+ yaml_lines.append("")
1213
+ # Split and wrap description into multiple lines
1214
+ yaml_lines.extend(wrap_text(description))
1215
+
1216
+ # Check if the field is deprecated and add deprecation notice
1217
+ if is_deprecated:
1218
+ # Add deprecation message
1219
+ if hasattr(field, "deprecation_message") and field.deprecation_message:
1220
+ yaml_lines.extend(
1221
+ wrap_text(f"Deprecated: {field.deprecation_message}")
1222
+ )
1223
+ else:
1224
+ yaml_lines.append("# Deprecated")
1225
+
1226
+ # Check if the field is experimental and add experimental notice
1227
+ if hasattr(field, "json_schema_extra") and field.json_schema_extra:
1228
+ if field.json_schema_extra.get("x-group") == "experimental":
1229
+ yaml_lines.append("# Experimental")
1230
+
1231
+ # Format the value based on its type
1232
+ if value is None:
1233
+ yaml_lines.append(f"{field_name}: null")
1234
+ elif isinstance(value, str):
1235
+ if "\n" in value or len(value) > 60:
1236
+ # Use block literal style (|) for multiline strings
1237
+ # Remove any existing escaped newlines and use actual line breaks
1238
+ value = value.replace("\\n", "\n")
1239
+ yaml_value = f"{field_name}: |-\n"
1240
+ # Indent each line with 2 spaces
1241
+ yaml_value += "\n".join(f" {line}" for line in value.split("\n"))
1242
+ yaml_lines.append(yaml_value)
1243
+ else:
1244
+ # Use flow style for short strings
1245
+ yaml_value = yaml.dump(
1246
+ {field_name: value},
1247
+ default_flow_style=False,
1248
+ allow_unicode=True, # This ensures emojis are preserved
1249
+ )
1250
+ yaml_lines.append(yaml_value.rstrip())
1251
+ elif isinstance(value, list) and value and hasattr(value[0], "model_dump"):
1252
+ # Handle list of Pydantic models (e.g., List[AgentAutonomous])
1253
+ yaml_lines.append(f"{field_name}:")
1254
+ # Convert each Pydantic model to dict
1255
+ model_dicts = [item.model_dump(exclude_none=True) for item in value]
1256
+ # Dump the list of dicts
1257
+ yaml_value = yaml.dump(
1258
+ model_dicts, default_flow_style=False, allow_unicode=True
1259
+ )
1260
+ # Indent all lines and append to yaml_lines
1261
+ indented_yaml = "\n".join(
1262
+ f" {line}" for line in yaml_value.split("\n")
1263
+ )
1264
+ yaml_lines.append(indented_yaml.rstrip())
1265
+ elif hasattr(value, "model_dump"):
1266
+ # Handle individual Pydantic model
1267
+ model_dict = value.model_dump(exclude_none=True)
1268
+ yaml_value = yaml.dump(
1269
+ {field_name: model_dict},
1270
+ default_flow_style=False,
1271
+ allow_unicode=True,
1272
+ )
1273
+ yaml_lines.append(yaml_value.rstrip())
1274
+ else:
1275
+ # Handle Decimal values specifically
1276
+ if isinstance(value, Decimal):
1277
+ # Convert Decimal to string to avoid !!python/object/apply:decimal.Decimal serialization
1278
+ yaml_lines.append(f"{field_name}: {value}")
1279
+ else:
1280
+ # Handle other non-string values
1281
+ yaml_value = yaml.dump(
1282
+ {field_name: value},
1283
+ default_flow_style=False,
1284
+ allow_unicode=True,
1285
+ )
1286
+ yaml_lines.append(yaml_value.rstrip())
1287
+
1288
+ return "\n".join(yaml_lines) + "\n"
1289
+
1290
+ @staticmethod
1291
+ async def count() -> int:
1292
+ async with get_session() as db:
1293
+ return await db.scalar(select(func.count(AgentTable.id)))
1294
+
1295
+ @classmethod
1296
+ async def get(cls, agent_id: str) -> Optional["Agent"]:
1297
+ async with get_session() as db:
1298
+ item = await db.scalar(select(AgentTable).where(AgentTable.id == agent_id))
1299
+ if item is None:
1300
+ return None
1301
+ return cls.model_validate(item)
1302
+
1303
+
1304
+ class AgentResponse(BaseModel):
1305
+ """Response model for Agent API."""
1306
+
1307
+ model_config = ConfigDict(
1308
+ from_attributes=True,
1309
+ json_encoders={
1310
+ datetime: lambda dt: dt.isoformat(),
1311
+ },
1312
+ )
1313
+
1314
+ id: Annotated[
1315
+ str,
1316
+ PydanticField(
1317
+ description="Unique identifier for the agent. Must be URL-safe, containing only lowercase letters, numbers, and hyphens",
1318
+ ),
1319
+ ]
1320
+ # auto increment number by db
1321
+ number: Annotated[
1322
+ int,
1323
+ PydanticField(
1324
+ description="Auto-incrementing number assigned by the system for easy reference",
1325
+ ),
1326
+ ]
1327
+ # auto timestamp
1328
+ created_at: Annotated[
1329
+ datetime,
1330
+ PydanticField(
1331
+ description="Timestamp when the agent was created, will ignore when importing"
1332
+ ),
1333
+ ]
1334
+ updated_at: Annotated[
1335
+ datetime,
1336
+ PydanticField(
1337
+ description="Timestamp when the agent was last updated, will ignore when importing"
1338
+ ),
1339
+ ]
1340
+ # Agent part
1341
+ name: Annotated[
1342
+ Optional[str],
1343
+ PydanticField(
1344
+ default=None,
1345
+ description="Display name of the agent",
1346
+ ),
1347
+ ]
1348
+ slug: Annotated[
1349
+ Optional[str],
1350
+ PydanticField(
1351
+ default=None,
1352
+ description="Slug of the agent, used for URL generation",
1353
+ ),
1354
+ ]
1355
+ description: Annotated[
1356
+ Optional[str],
1357
+ PydanticField(
1358
+ default=None,
1359
+ description="Description of the agent, for public view, not contained in prompt",
1360
+ ),
1361
+ ]
1362
+ external_website: Annotated[
1363
+ Optional[str],
1364
+ PydanticField(
1365
+ default=None,
1366
+ description="Link of external website of the agent, if you have one",
1367
+ ),
1368
+ ]
1369
+ picture: Annotated[
1370
+ Optional[str],
1371
+ PydanticField(
1372
+ default=None,
1373
+ description="Picture of the agent",
1374
+ ),
1375
+ ]
1376
+ ticker: Annotated[
1377
+ Optional[str],
1378
+ PydanticField(
1379
+ default=None,
1380
+ description="Ticker symbol of the agent",
1381
+ ),
1382
+ ]
1383
+ token_address: Annotated[
1384
+ Optional[str],
1385
+ PydanticField(
1386
+ default=None,
1387
+ description="Token address of the agent",
1388
+ ),
1389
+ ]
1390
+ token_pool: Annotated[
1391
+ Optional[str],
1392
+ PydanticField(
1393
+ default=None,
1394
+ description="Pool of the agent token",
1395
+ ),
1396
+ ]
1397
+ mode: Annotated[
1398
+ Optional[Literal["public", "private"]],
1399
+ PydanticField(
1400
+ default=None,
1401
+ description="Mode of the agent, public or private",
1402
+ ),
1403
+ ]
1404
+ fee_percentage: Annotated[
1405
+ Optional[Decimal],
1406
+ PydanticField(
1407
+ default=None,
1408
+ description="Fee percentage of the agent",
1409
+ ),
1410
+ ]
1411
+ owner: Annotated[
1412
+ Optional[str],
1413
+ PydanticField(
1414
+ default=None,
1415
+ description="Owner identifier of the agent, used for access control",
1416
+ max_length=50,
1417
+ json_schema_extra={
1418
+ "x-group": "internal",
1419
+ },
1420
+ ),
1421
+ ]
1422
+ upstream_id: Annotated[
1423
+ Optional[str],
1424
+ PydanticField(
1425
+ default=None,
1426
+ description="External reference ID for idempotent operations",
1427
+ max_length=100,
1428
+ json_schema_extra={
1429
+ "x-group": "internal",
1430
+ },
1431
+ ),
1432
+ ]
1433
+ upstream_extra: Annotated[
1434
+ Optional[Dict[str, Any]],
1435
+ PydanticField(
1436
+ default=None,
1437
+ description="Additional data store for upstream use",
1438
+ ),
1439
+ ]
1440
+ # AI part
1441
+ model: Annotated[
1442
+ str,
1443
+ PydanticField(
1444
+ description="AI model identifier to be used by this agent for processing requests. Available models: gpt-4o, gpt-4o-mini, deepseek-chat, deepseek-reasoner, grok-2, eternalai, reigent",
1445
+ ),
1446
+ ]
1447
+ # autonomous mode
1448
+ autonomous: Annotated[
1449
+ Optional[List[Dict[str, Any]]],
1450
+ PydanticField(
1451
+ default=None,
1452
+ description=("Autonomous agent configurations."),
1453
+ ),
1454
+ ]
1455
+ # agent examples
1456
+ example_intro: Annotated[
1457
+ Optional[str],
1458
+ PydanticField(
1459
+ default=None,
1460
+ description="Introduction for example interactions",
1461
+ ),
1462
+ ]
1463
+ examples: Annotated[
1464
+ Optional[List[AgentExample]],
1465
+ PydanticField(
1466
+ default=None,
1467
+ description="List of example prompts for the agent",
1468
+ ),
1469
+ ]
1470
+ # skills
1471
+ skills: Annotated[
1472
+ Optional[Dict[str, Any]],
1473
+ PydanticField(
1474
+ default=None,
1475
+ description="Dict of skills and their corresponding configurations",
1476
+ ),
1477
+ ]
1478
+ wallet_provider: Annotated[
1479
+ Optional[Literal["cdp"]],
1480
+ PydanticField(
1481
+ default="cdp",
1482
+ description="Provider of the agent's wallet",
1483
+ ),
1484
+ ]
1485
+ network_id: Annotated[
1486
+ Optional[str],
1487
+ PydanticField(
1488
+ default="base-mainnet",
1489
+ description="Network identifier",
1490
+ ),
1491
+ ]
1492
+ cdp_network_id: Annotated[
1493
+ Optional[str],
1494
+ PydanticField(
1495
+ default="base-mainnet",
1496
+ description="Network identifier for CDP integration",
1497
+ ),
1498
+ ]
1499
+ # telegram entrypoint
1500
+ telegram_entrypoint_enabled: Annotated[
1501
+ Optional[bool],
1502
+ PydanticField(
1503
+ default=False,
1504
+ description="Whether the agent can play telegram bot",
1505
+ ),
1506
+ ]
1507
+
1508
+ # data part
1509
+ cdp_wallet_address: Annotated[
1510
+ Optional[str], PydanticField(description="CDP wallet address for the agent")
1511
+ ]
1512
+ has_twitter_linked: Annotated[
1513
+ bool,
1514
+ PydanticField(description="Whether the agent has linked their Twitter account"),
1515
+ ]
1516
+ linked_twitter_username: Annotated[
1517
+ Optional[str],
1518
+ PydanticField(description="The username of the linked Twitter account"),
1519
+ ]
1520
+ linked_twitter_name: Annotated[
1521
+ Optional[str],
1522
+ PydanticField(description="The name of the linked Twitter account"),
1523
+ ]
1524
+ has_twitter_self_key: Annotated[
1525
+ bool,
1526
+ PydanticField(
1527
+ description="Whether the agent has self-keyed their Twitter account"
1528
+ ),
1529
+ ]
1530
+ has_telegram_self_key: Annotated[
1531
+ bool,
1532
+ PydanticField(
1533
+ description="Whether the agent has self-keyed their Telegram account"
1534
+ ),
1535
+ ]
1536
+ linked_telegram_username: Annotated[
1537
+ Optional[str],
1538
+ PydanticField(description="The username of the linked Telegram account"),
1539
+ ]
1540
+ linked_telegram_name: Annotated[
1541
+ Optional[str],
1542
+ PydanticField(description="The name of the linked Telegram account"),
1543
+ ]
1544
+ accept_image_input: Annotated[
1545
+ bool,
1546
+ PydanticField(
1547
+ description="Whether the agent accepts image inputs in public mode"
1548
+ ),
1549
+ ]
1550
+ accept_image_input_private: Annotated[
1551
+ bool,
1552
+ PydanticField(
1553
+ description="Whether the agent accepts image inputs in private mode"
1554
+ ),
1555
+ ]
1556
+
1557
+ def etag(self) -> str:
1558
+ """Generate an ETag for this agent response.
1559
+
1560
+ The ETag is based on a hash of the entire object to ensure it changes
1561
+ whenever any part of the agent is modified.
1562
+
1563
+ Returns:
1564
+ str: ETag value for the agent
1565
+ """
1566
+ import hashlib
1567
+ import json
1568
+
1569
+ # Generate hash from the entire object data using json mode to handle datetime objects
1570
+ # Sort keys to ensure consistent ordering of dictionary keys
1571
+ data = json.dumps(self.model_dump(mode="json"), sort_keys=True)
1572
+ return f"{hashlib.md5(data.encode()).hexdigest()}"
1573
+
1574
+ @classmethod
1575
+ async def from_agent(
1576
+ cls, agent: Agent, agent_data: Optional[AgentData] = None
1577
+ ) -> "AgentResponse":
1578
+ """Create an AgentResponse from an Agent instance.
1579
+
1580
+ Args:
1581
+ agent: Agent instance
1582
+ agent_data: Optional AgentData instance
1583
+
1584
+ Returns:
1585
+ AgentResponse: Response model with additional processed data
1586
+ """
1587
+ # Get base data from agent
1588
+ data = agent.model_dump()
1589
+
1590
+ # Filter sensitive fields from autonomous list
1591
+ if data.get("autonomous"):
1592
+ filtered_autonomous = []
1593
+ for item in data["autonomous"]:
1594
+ if isinstance(item, dict):
1595
+ filtered_item = {
1596
+ "id": item.get("id"),
1597
+ "name": item.get("name"),
1598
+ "enabled": item.get("enabled"),
1599
+ }
1600
+ filtered_autonomous.append(filtered_item)
1601
+ data["autonomous"] = filtered_autonomous
1602
+
1603
+ # Filter sensitive fields from skills dictionary
1604
+ if data.get("skills"):
1605
+ filtered_skills = {}
1606
+ for skill_name, skill_config in data["skills"].items():
1607
+ if isinstance(skill_config, dict):
1608
+ # Only include skills that are enabled
1609
+ if skill_config.get("enabled") is True:
1610
+ filtered_config = {"enabled": True}
1611
+ # Only keep states with public or private values
1612
+ if "states" in skill_config:
1613
+ filtered_states = {}
1614
+ for state_key, state_value in skill_config[
1615
+ "states"
1616
+ ].items():
1617
+ if state_value in ["public", "private"]:
1618
+ filtered_states[state_key] = state_value
1619
+ if filtered_states:
1620
+ filtered_config["states"] = filtered_states
1621
+ filtered_skills[skill_name] = filtered_config
1622
+ data["skills"] = filtered_skills
1623
+
1624
+ # Process CDP wallet address
1625
+ cdp_wallet_address = None
1626
+ if agent_data and agent_data.cdp_wallet_data:
1627
+ try:
1628
+ wallet_data = json.loads(agent_data.cdp_wallet_data)
1629
+ cdp_wallet_address = wallet_data.get("default_address_id")
1630
+ except (json.JSONDecodeError, AttributeError):
1631
+ pass
1632
+
1633
+ # Process Twitter linked status
1634
+ has_twitter_linked = False
1635
+ linked_twitter_username = None
1636
+ linked_twitter_name = None
1637
+ if agent_data and agent_data.twitter_access_token:
1638
+ linked_twitter_username = agent_data.twitter_username
1639
+ linked_twitter_name = agent_data.twitter_name
1640
+ if agent_data.twitter_access_token_expires_at:
1641
+ has_twitter_linked = (
1642
+ agent_data.twitter_access_token_expires_at
1643
+ > datetime.now(timezone.utc)
1644
+ )
1645
+ else:
1646
+ has_twitter_linked = True
1647
+
1648
+ # Process Twitter self-key status
1649
+ has_twitter_self_key = bool(
1650
+ agent_data and agent_data.twitter_self_key_refreshed_at
1651
+ )
1652
+
1653
+ # Process Telegram self-key status and remove token
1654
+ linked_telegram_username = None
1655
+ linked_telegram_name = None
1656
+ telegram_config = data.get("telegram_config", {})
1657
+ has_telegram_self_key = bool(
1658
+ telegram_config and "token" in telegram_config and telegram_config["token"]
1659
+ )
1660
+ if telegram_config and "token" in telegram_config:
1661
+ if agent_data:
1662
+ linked_telegram_username = agent_data.telegram_username
1663
+ linked_telegram_name = agent_data.telegram_name
1664
+
1665
+ accept_image_input = (
1666
+ await agent.is_model_support_image() or agent.has_image_parser_skill()
1667
+ )
1668
+ accept_image_input_private = (
1669
+ await agent.is_model_support_image()
1670
+ or agent.has_image_parser_skill(is_private=True)
1671
+ )
1672
+
1673
+ # Add processed fields to response
1674
+ data.update(
1675
+ {
1676
+ "cdp_wallet_address": cdp_wallet_address,
1677
+ "has_twitter_linked": has_twitter_linked,
1678
+ "linked_twitter_username": linked_twitter_username,
1679
+ "linked_twitter_name": linked_twitter_name,
1680
+ "has_twitter_self_key": has_twitter_self_key,
1681
+ "has_telegram_self_key": has_telegram_self_key,
1682
+ "linked_telegram_username": linked_telegram_username,
1683
+ "linked_telegram_name": linked_telegram_name,
1684
+ "accept_image_input": accept_image_input,
1685
+ "accept_image_input_private": accept_image_input_private,
1686
+ }
1687
+ )
1688
+
1689
+ return cls.model_validate(data)