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.
- intentkit/__init__.py +17 -0
- intentkit/abstracts/__init__.py +0 -0
- intentkit/abstracts/agent.py +60 -0
- intentkit/abstracts/api.py +4 -0
- intentkit/abstracts/engine.py +38 -0
- intentkit/abstracts/exception.py +9 -0
- intentkit/abstracts/graph.py +25 -0
- intentkit/abstracts/skill.py +129 -0
- intentkit/abstracts/twitter.py +54 -0
- intentkit/clients/__init__.py +14 -0
- intentkit/clients/cdp.py +53 -0
- intentkit/clients/twitter.py +445 -0
- intentkit/config/__init__.py +0 -0
- intentkit/config/config.py +164 -0
- intentkit/core/__init__.py +0 -0
- intentkit/core/agent.py +191 -0
- intentkit/core/api.py +40 -0
- intentkit/core/client.py +45 -0
- intentkit/core/credit.py +1767 -0
- intentkit/core/engine.py +1018 -0
- intentkit/core/node.py +223 -0
- intentkit/core/prompt.py +58 -0
- intentkit/core/skill.py +124 -0
- intentkit/models/agent.py +1689 -0
- intentkit/models/agent_data.py +810 -0
- intentkit/models/agent_schema.json +733 -0
- intentkit/models/app_setting.py +156 -0
- intentkit/models/base.py +9 -0
- intentkit/models/chat.py +581 -0
- intentkit/models/conversation.py +286 -0
- intentkit/models/credit.py +1406 -0
- intentkit/models/db.py +120 -0
- intentkit/models/db_mig.py +102 -0
- intentkit/models/generator.py +347 -0
- intentkit/models/llm.py +746 -0
- intentkit/models/redis.py +132 -0
- intentkit/models/skill.py +466 -0
- intentkit/models/user.py +243 -0
- intentkit/skills/__init__.py +12 -0
- intentkit/skills/acolyt/__init__.py +83 -0
- intentkit/skills/acolyt/acolyt.jpg +0 -0
- intentkit/skills/acolyt/ask.py +128 -0
- intentkit/skills/acolyt/base.py +28 -0
- intentkit/skills/acolyt/schema.json +89 -0
- intentkit/skills/aixbt/README.md +71 -0
- intentkit/skills/aixbt/__init__.py +73 -0
- intentkit/skills/aixbt/aixbt.jpg +0 -0
- intentkit/skills/aixbt/base.py +21 -0
- intentkit/skills/aixbt/projects.py +153 -0
- intentkit/skills/aixbt/schema.json +99 -0
- intentkit/skills/allora/__init__.py +83 -0
- intentkit/skills/allora/allora.jpeg +0 -0
- intentkit/skills/allora/base.py +28 -0
- intentkit/skills/allora/price.py +130 -0
- intentkit/skills/allora/schema.json +89 -0
- intentkit/skills/base.py +174 -0
- intentkit/skills/carv/README.md +95 -0
- intentkit/skills/carv/__init__.py +121 -0
- intentkit/skills/carv/base.py +183 -0
- intentkit/skills/carv/carv.webp +0 -0
- intentkit/skills/carv/fetch_news.py +92 -0
- intentkit/skills/carv/onchain_query.py +164 -0
- intentkit/skills/carv/schema.json +137 -0
- intentkit/skills/carv/token_info_and_price.py +110 -0
- intentkit/skills/cdp/__init__.py +137 -0
- intentkit/skills/cdp/base.py +21 -0
- intentkit/skills/cdp/cdp.png +0 -0
- intentkit/skills/cdp/get_balance.py +81 -0
- intentkit/skills/cdp/schema.json +473 -0
- intentkit/skills/chainlist/README.md +38 -0
- intentkit/skills/chainlist/__init__.py +54 -0
- intentkit/skills/chainlist/base.py +21 -0
- intentkit/skills/chainlist/chain_lookup.py +208 -0
- intentkit/skills/chainlist/chainlist.png +0 -0
- intentkit/skills/chainlist/schema.json +47 -0
- intentkit/skills/common/__init__.py +82 -0
- intentkit/skills/common/base.py +21 -0
- intentkit/skills/common/common.jpg +0 -0
- intentkit/skills/common/current_time.py +84 -0
- intentkit/skills/common/schema.json +57 -0
- intentkit/skills/cookiefun/README.md +121 -0
- intentkit/skills/cookiefun/__init__.py +78 -0
- intentkit/skills/cookiefun/base.py +41 -0
- intentkit/skills/cookiefun/constants.py +18 -0
- intentkit/skills/cookiefun/cookiefun.png +0 -0
- intentkit/skills/cookiefun/get_account_details.py +171 -0
- intentkit/skills/cookiefun/get_account_feed.py +282 -0
- intentkit/skills/cookiefun/get_account_smart_followers.py +181 -0
- intentkit/skills/cookiefun/get_sectors.py +128 -0
- intentkit/skills/cookiefun/schema.json +155 -0
- intentkit/skills/cookiefun/search_accounts.py +225 -0
- intentkit/skills/cryptocompare/__init__.py +130 -0
- intentkit/skills/cryptocompare/api.py +159 -0
- intentkit/skills/cryptocompare/base.py +303 -0
- intentkit/skills/cryptocompare/cryptocompare.png +0 -0
- intentkit/skills/cryptocompare/fetch_news.py +96 -0
- intentkit/skills/cryptocompare/fetch_price.py +99 -0
- intentkit/skills/cryptocompare/fetch_top_exchanges.py +113 -0
- intentkit/skills/cryptocompare/fetch_top_market_cap.py +109 -0
- intentkit/skills/cryptocompare/fetch_top_volume.py +108 -0
- intentkit/skills/cryptocompare/fetch_trading_signals.py +107 -0
- intentkit/skills/cryptocompare/schema.json +168 -0
- intentkit/skills/cryptopanic/__init__.py +108 -0
- intentkit/skills/cryptopanic/base.py +51 -0
- intentkit/skills/cryptopanic/cryptopanic.png +0 -0
- intentkit/skills/cryptopanic/fetch_crypto_news.py +153 -0
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +136 -0
- intentkit/skills/cryptopanic/schema.json +103 -0
- intentkit/skills/dapplooker/README.md +92 -0
- intentkit/skills/dapplooker/__init__.py +83 -0
- intentkit/skills/dapplooker/base.py +26 -0
- intentkit/skills/dapplooker/dapplooker.jpg +0 -0
- intentkit/skills/dapplooker/dapplooker_token_data.py +476 -0
- intentkit/skills/dapplooker/schema.json +91 -0
- intentkit/skills/defillama/__init__.py +323 -0
- intentkit/skills/defillama/api.py +315 -0
- intentkit/skills/defillama/base.py +135 -0
- intentkit/skills/defillama/coins/__init__.py +0 -0
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +116 -0
- intentkit/skills/defillama/coins/fetch_block.py +98 -0
- intentkit/skills/defillama/coins/fetch_current_prices.py +105 -0
- intentkit/skills/defillama/coins/fetch_first_price.py +100 -0
- intentkit/skills/defillama/coins/fetch_historical_prices.py +110 -0
- intentkit/skills/defillama/coins/fetch_price_chart.py +109 -0
- intentkit/skills/defillama/coins/fetch_price_percentage.py +93 -0
- intentkit/skills/defillama/config/__init__.py +0 -0
- intentkit/skills/defillama/config/chains.py +433 -0
- intentkit/skills/defillama/defillama.jpeg +0 -0
- intentkit/skills/defillama/fees/__init__.py +0 -0
- intentkit/skills/defillama/fees/fetch_fees_overview.py +130 -0
- intentkit/skills/defillama/schema.json +383 -0
- intentkit/skills/defillama/stablecoins/__init__.py +0 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +100 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +129 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +83 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +126 -0
- intentkit/skills/defillama/tests/__init__.py +0 -0
- intentkit/skills/defillama/tests/api_integration.test.py +192 -0
- intentkit/skills/defillama/tests/api_unit.test.py +583 -0
- intentkit/skills/defillama/tvl/__init__.py +0 -0
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +106 -0
- intentkit/skills/defillama/tvl/fetch_chains.py +107 -0
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +91 -0
- intentkit/skills/defillama/tvl/fetch_protocol.py +207 -0
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +93 -0
- intentkit/skills/defillama/tvl/fetch_protocols.py +196 -0
- intentkit/skills/defillama/volumes/__init__.py +0 -0
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +157 -0
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +123 -0
- intentkit/skills/defillama/volumes/fetch_options_overview.py +131 -0
- intentkit/skills/defillama/yields/__init__.py +0 -0
- intentkit/skills/defillama/yields/fetch_pool_chart.py +100 -0
- intentkit/skills/defillama/yields/fetch_pools.py +126 -0
- intentkit/skills/dexscreener/__init__.py +93 -0
- intentkit/skills/dexscreener/base.py +133 -0
- intentkit/skills/dexscreener/dexscreener.png +0 -0
- intentkit/skills/dexscreener/model/__init__.py +0 -0
- intentkit/skills/dexscreener/model/search_token_response.py +82 -0
- intentkit/skills/dexscreener/schema.json +48 -0
- intentkit/skills/dexscreener/search_token.py +321 -0
- intentkit/skills/dune_analytics/__init__.py +103 -0
- intentkit/skills/dune_analytics/base.py +46 -0
- intentkit/skills/dune_analytics/dune.png +0 -0
- intentkit/skills/dune_analytics/fetch_kol_buys.py +128 -0
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +237 -0
- intentkit/skills/dune_analytics/schema.json +99 -0
- intentkit/skills/elfa/README.md +100 -0
- intentkit/skills/elfa/__init__.py +123 -0
- intentkit/skills/elfa/base.py +28 -0
- intentkit/skills/elfa/elfa.jpg +0 -0
- intentkit/skills/elfa/mention.py +504 -0
- intentkit/skills/elfa/schema.json +153 -0
- intentkit/skills/elfa/stats.py +118 -0
- intentkit/skills/elfa/tokens.py +126 -0
- intentkit/skills/enso/README.md +75 -0
- intentkit/skills/enso/__init__.py +114 -0
- intentkit/skills/enso/abi/__init__.py +0 -0
- intentkit/skills/enso/abi/approval.py +279 -0
- intentkit/skills/enso/abi/erc20.py +14 -0
- intentkit/skills/enso/abi/route.py +129 -0
- intentkit/skills/enso/base.py +44 -0
- intentkit/skills/enso/best_yield.py +286 -0
- intentkit/skills/enso/enso.jpg +0 -0
- intentkit/skills/enso/networks.py +105 -0
- intentkit/skills/enso/prices.py +93 -0
- intentkit/skills/enso/route.py +300 -0
- intentkit/skills/enso/schema.json +212 -0
- intentkit/skills/enso/tokens.py +223 -0
- intentkit/skills/enso/wallet.py +381 -0
- intentkit/skills/github/README.md +63 -0
- intentkit/skills/github/__init__.py +54 -0
- intentkit/skills/github/base.py +21 -0
- intentkit/skills/github/github.jpg +0 -0
- intentkit/skills/github/github_search.py +183 -0
- intentkit/skills/github/schema.json +59 -0
- intentkit/skills/heurist/__init__.py +143 -0
- intentkit/skills/heurist/base.py +26 -0
- intentkit/skills/heurist/heurist.png +0 -0
- intentkit/skills/heurist/image_generation_animagine_xl.py +162 -0
- intentkit/skills/heurist/image_generation_arthemy_comics.py +162 -0
- intentkit/skills/heurist/image_generation_arthemy_real.py +162 -0
- intentkit/skills/heurist/image_generation_braindance.py +162 -0
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +162 -0
- intentkit/skills/heurist/image_generation_flux_1_dev.py +162 -0
- intentkit/skills/heurist/image_generation_sdxl.py +161 -0
- intentkit/skills/heurist/schema.json +196 -0
- intentkit/skills/lifi/README.md +294 -0
- intentkit/skills/lifi/__init__.py +141 -0
- intentkit/skills/lifi/base.py +21 -0
- intentkit/skills/lifi/lifi.png +0 -0
- intentkit/skills/lifi/schema.json +89 -0
- intentkit/skills/lifi/token_execute.py +472 -0
- intentkit/skills/lifi/token_quote.py +190 -0
- intentkit/skills/lifi/utils.py +656 -0
- intentkit/skills/moralis/README.md +490 -0
- intentkit/skills/moralis/__init__.py +110 -0
- intentkit/skills/moralis/api.py +281 -0
- intentkit/skills/moralis/base.py +55 -0
- intentkit/skills/moralis/fetch_chain_portfolio.py +191 -0
- intentkit/skills/moralis/fetch_nft_portfolio.py +284 -0
- intentkit/skills/moralis/fetch_solana_portfolio.py +331 -0
- intentkit/skills/moralis/fetch_wallet_portfolio.py +301 -0
- intentkit/skills/moralis/moralis.png +0 -0
- intentkit/skills/moralis/schema.json +156 -0
- intentkit/skills/moralis/tests/__init__.py +0 -0
- intentkit/skills/moralis/tests/test_wallet.py +511 -0
- intentkit/skills/nation/__init__.py +62 -0
- intentkit/skills/nation/base.py +31 -0
- intentkit/skills/nation/nation.png +0 -0
- intentkit/skills/nation/nft_check.py +106 -0
- intentkit/skills/nation/schema.json +58 -0
- intentkit/skills/openai/__init__.py +107 -0
- intentkit/skills/openai/base.py +32 -0
- intentkit/skills/openai/dalle_image_generation.py +128 -0
- intentkit/skills/openai/gpt_image_generation.py +152 -0
- intentkit/skills/openai/gpt_image_to_image.py +186 -0
- intentkit/skills/openai/image_to_text.py +126 -0
- intentkit/skills/openai/openai.png +0 -0
- intentkit/skills/openai/schema.json +139 -0
- intentkit/skills/portfolio/README.md +55 -0
- intentkit/skills/portfolio/__init__.py +151 -0
- intentkit/skills/portfolio/base.py +107 -0
- intentkit/skills/portfolio/constants.py +9 -0
- intentkit/skills/portfolio/moralis.png +0 -0
- intentkit/skills/portfolio/schema.json +237 -0
- intentkit/skills/portfolio/token_balances.py +155 -0
- intentkit/skills/portfolio/wallet_approvals.py +102 -0
- intentkit/skills/portfolio/wallet_defi_positions.py +80 -0
- intentkit/skills/portfolio/wallet_history.py +155 -0
- intentkit/skills/portfolio/wallet_net_worth.py +112 -0
- intentkit/skills/portfolio/wallet_nfts.py +139 -0
- intentkit/skills/portfolio/wallet_profitability.py +101 -0
- intentkit/skills/portfolio/wallet_profitability_summary.py +91 -0
- intentkit/skills/portfolio/wallet_stats.py +79 -0
- intentkit/skills/portfolio/wallet_swaps.py +147 -0
- intentkit/skills/skills.toml +103 -0
- intentkit/skills/slack/__init__.py +98 -0
- intentkit/skills/slack/base.py +55 -0
- intentkit/skills/slack/get_channel.py +109 -0
- intentkit/skills/slack/get_message.py +136 -0
- intentkit/skills/slack/schedule_message.py +92 -0
- intentkit/skills/slack/schema.json +135 -0
- intentkit/skills/slack/send_message.py +81 -0
- intentkit/skills/slack/slack.jpg +0 -0
- intentkit/skills/system/__init__.py +90 -0
- intentkit/skills/system/base.py +22 -0
- intentkit/skills/system/read_agent_api_key.py +87 -0
- intentkit/skills/system/regenerate_agent_api_key.py +77 -0
- intentkit/skills/system/schema.json +53 -0
- intentkit/skills/system/system.svg +76 -0
- intentkit/skills/tavily/README.md +86 -0
- intentkit/skills/tavily/__init__.py +91 -0
- intentkit/skills/tavily/base.py +27 -0
- intentkit/skills/tavily/schema.json +119 -0
- intentkit/skills/tavily/tavily.jpg +0 -0
- intentkit/skills/tavily/tavily_extract.py +147 -0
- intentkit/skills/tavily/tavily_search.py +139 -0
- intentkit/skills/token/README.md +89 -0
- intentkit/skills/token/__init__.py +107 -0
- intentkit/skills/token/base.py +154 -0
- intentkit/skills/token/constants.py +9 -0
- intentkit/skills/token/erc20_transfers.py +145 -0
- intentkit/skills/token/moralis.png +0 -0
- intentkit/skills/token/schema.json +141 -0
- intentkit/skills/token/token_analytics.py +81 -0
- intentkit/skills/token/token_price.py +132 -0
- intentkit/skills/token/token_search.py +121 -0
- intentkit/skills/twitter/__init__.py +146 -0
- intentkit/skills/twitter/base.py +68 -0
- intentkit/skills/twitter/follow_user.py +69 -0
- intentkit/skills/twitter/get_mentions.py +124 -0
- intentkit/skills/twitter/get_timeline.py +111 -0
- intentkit/skills/twitter/get_user_by_username.py +84 -0
- intentkit/skills/twitter/get_user_tweets.py +123 -0
- intentkit/skills/twitter/like_tweet.py +65 -0
- intentkit/skills/twitter/post_tweet.py +90 -0
- intentkit/skills/twitter/reply_tweet.py +98 -0
- intentkit/skills/twitter/retweet.py +76 -0
- intentkit/skills/twitter/schema.json +258 -0
- intentkit/skills/twitter/search_tweets.py +115 -0
- intentkit/skills/twitter/twitter.png +0 -0
- intentkit/skills/unrealspeech/__init__.py +55 -0
- intentkit/skills/unrealspeech/base.py +21 -0
- intentkit/skills/unrealspeech/schema.json +100 -0
- intentkit/skills/unrealspeech/text_to_speech.py +177 -0
- intentkit/skills/unrealspeech/unrealspeech.jpg +0 -0
- intentkit/skills/venice_audio/__init__.py +106 -0
- intentkit/skills/venice_audio/base.py +119 -0
- intentkit/skills/venice_audio/input.py +41 -0
- intentkit/skills/venice_audio/schema.json +152 -0
- intentkit/skills/venice_audio/venice_audio.py +240 -0
- intentkit/skills/venice_audio/venice_logo.jpg +0 -0
- intentkit/skills/venice_image/README.md +119 -0
- intentkit/skills/venice_image/__init__.py +154 -0
- intentkit/skills/venice_image/api.py +138 -0
- intentkit/skills/venice_image/base.py +188 -0
- intentkit/skills/venice_image/config.py +35 -0
- intentkit/skills/venice_image/image_enhance/README.md +119 -0
- intentkit/skills/venice_image/image_enhance/__init__.py +0 -0
- intentkit/skills/venice_image/image_enhance/image_enhance.py +80 -0
- intentkit/skills/venice_image/image_enhance/image_enhance_base.py +23 -0
- intentkit/skills/venice_image/image_enhance/image_enhance_input.py +40 -0
- intentkit/skills/venice_image/image_generation/README.md +144 -0
- intentkit/skills/venice_image/image_generation/__init__.py +0 -0
- intentkit/skills/venice_image/image_generation/image_generation_base.py +117 -0
- intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -0
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -0
- intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -0
- intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -0
- intentkit/skills/venice_image/image_upscale/README.md +111 -0
- intentkit/skills/venice_image/image_upscale/__init__.py +0 -0
- intentkit/skills/venice_image/image_upscale/image_upscale.py +90 -0
- intentkit/skills/venice_image/image_upscale/image_upscale_base.py +23 -0
- intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -0
- intentkit/skills/venice_image/image_vision/README.md +112 -0
- intentkit/skills/venice_image/image_vision/__init__.py +0 -0
- intentkit/skills/venice_image/image_vision/image_vision.py +100 -0
- intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -0
- intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -0
- intentkit/skills/venice_image/schema.json +267 -0
- intentkit/skills/venice_image/utils.py +78 -0
- intentkit/skills/venice_image/venice_image.jpg +0 -0
- intentkit/skills/web_scraper/README.md +82 -0
- intentkit/skills/web_scraper/__init__.py +92 -0
- intentkit/skills/web_scraper/base.py +21 -0
- intentkit/skills/web_scraper/langchain.png +0 -0
- intentkit/skills/web_scraper/schema.json +115 -0
- intentkit/skills/web_scraper/scrape_and_index.py +327 -0
- intentkit/utils/__init__.py +1 -0
- intentkit/utils/chain.py +436 -0
- intentkit/utils/error.py +134 -0
- intentkit/utils/logging.py +70 -0
- intentkit/utils/middleware.py +61 -0
- intentkit/utils/random.py +16 -0
- intentkit/utils/s3.py +267 -0
- intentkit/utils/slack_alert.py +79 -0
- intentkit/utils/tx.py +37 -0
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/METADATA +1 -1
- intentkit-0.5.2.dist-info/RECORD +365 -0
- intentkit-0.5.0.dist-info/RECORD +0 -4
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/WHEEL +0 -0
- {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)
|