intentkit 0.7.5.dev3__py3-none-any.whl → 0.8.34.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- intentkit/MANIFEST.in +14 -0
- intentkit/README.md +88 -0
- intentkit/__init__.py +6 -4
- intentkit/abstracts/agent.py +4 -5
- intentkit/abstracts/engine.py +5 -5
- intentkit/abstracts/graph.py +15 -8
- intentkit/abstracts/skill.py +6 -144
- intentkit/abstracts/twitter.py +4 -5
- intentkit/clients/__init__.py +9 -2
- intentkit/clients/cdp.py +129 -153
- intentkit/{utils → clients}/s3.py +109 -34
- intentkit/clients/twitter.py +83 -62
- intentkit/clients/web3.py +4 -7
- intentkit/config/config.py +123 -90
- intentkit/core/account_checking.py +802 -0
- intentkit/core/agent.py +313 -498
- intentkit/core/asset.py +267 -0
- intentkit/core/chat.py +5 -3
- intentkit/core/client.py +1 -1
- intentkit/core/credit.py +49 -41
- intentkit/core/draft.py +201 -0
- intentkit/core/draft_chat.py +118 -0
- intentkit/core/engine.py +378 -287
- intentkit/core/manager/__init__.py +25 -0
- intentkit/core/manager/engine.py +220 -0
- intentkit/core/manager/service.py +172 -0
- intentkit/core/manager/skills.py +178 -0
- intentkit/core/middleware.py +231 -0
- intentkit/core/prompt.py +74 -114
- intentkit/core/scheduler.py +143 -0
- intentkit/core/statistics.py +168 -0
- intentkit/models/agent.py +931 -518
- intentkit/models/agent_data.py +165 -106
- intentkit/models/agent_schema.json +38 -251
- intentkit/models/app_setting.py +15 -13
- intentkit/models/chat.py +86 -140
- intentkit/models/credit.py +182 -162
- intentkit/models/db.py +42 -23
- intentkit/models/db_mig.py +120 -3
- intentkit/models/draft.py +222 -0
- intentkit/models/llm.csv +31 -0
- intentkit/models/llm.py +262 -370
- intentkit/models/redis.py +6 -4
- intentkit/models/skill.py +222 -101
- intentkit/models/skills.csv +173 -0
- intentkit/models/team.py +189 -0
- intentkit/models/user.py +103 -31
- intentkit/skills/acolyt/__init__.py +2 -9
- intentkit/skills/acolyt/ask.py +3 -4
- intentkit/skills/acolyt/base.py +4 -9
- intentkit/skills/acolyt/schema.json +4 -3
- intentkit/skills/aixbt/__init__.py +2 -13
- intentkit/skills/aixbt/base.py +1 -7
- intentkit/skills/aixbt/projects.py +14 -15
- intentkit/skills/aixbt/schema.json +4 -4
- intentkit/skills/allora/__init__.py +2 -9
- intentkit/skills/allora/base.py +4 -9
- intentkit/skills/allora/price.py +3 -4
- intentkit/skills/allora/schema.json +3 -2
- intentkit/skills/base.py +241 -41
- intentkit/skills/basename/__init__.py +51 -0
- intentkit/skills/basename/base.py +11 -0
- intentkit/skills/basename/basename.svg +11 -0
- intentkit/skills/basename/schema.json +58 -0
- intentkit/skills/carv/__init__.py +115 -121
- intentkit/skills/carv/base.py +184 -185
- intentkit/skills/carv/fetch_news.py +3 -3
- intentkit/skills/carv/onchain_query.py +4 -4
- intentkit/skills/carv/schema.json +134 -137
- intentkit/skills/carv/token_info_and_price.py +6 -6
- intentkit/skills/casino/__init__.py +4 -15
- intentkit/skills/casino/base.py +1 -7
- intentkit/skills/casino/deck_draw.py +5 -8
- intentkit/skills/casino/deck_shuffle.py +6 -6
- intentkit/skills/casino/dice_roll.py +2 -4
- intentkit/skills/casino/schema.json +0 -1
- intentkit/skills/cdp/__init__.py +22 -84
- intentkit/skills/cdp/base.py +1 -7
- intentkit/skills/cdp/schema.json +11 -314
- intentkit/skills/chainlist/__init__.py +2 -7
- intentkit/skills/chainlist/base.py +1 -7
- intentkit/skills/chainlist/chain_lookup.py +18 -18
- intentkit/skills/chainlist/schema.json +3 -5
- intentkit/skills/common/__init__.py +2 -9
- intentkit/skills/common/base.py +1 -7
- intentkit/skills/common/current_time.py +1 -2
- intentkit/skills/common/schema.json +2 -2
- intentkit/skills/cookiefun/__init__.py +6 -9
- intentkit/skills/cookiefun/base.py +2 -7
- intentkit/skills/cookiefun/get_account_details.py +7 -7
- intentkit/skills/cookiefun/get_account_feed.py +19 -19
- intentkit/skills/cookiefun/get_account_smart_followers.py +7 -7
- intentkit/skills/cookiefun/get_sectors.py +3 -3
- intentkit/skills/cookiefun/schema.json +1 -3
- intentkit/skills/cookiefun/search_accounts.py +9 -9
- intentkit/skills/cryptocompare/__init__.py +7 -24
- intentkit/skills/cryptocompare/api.py +2 -3
- intentkit/skills/cryptocompare/base.py +10 -24
- intentkit/skills/cryptocompare/fetch_news.py +4 -5
- intentkit/skills/cryptocompare/fetch_price.py +6 -7
- intentkit/skills/cryptocompare/fetch_top_exchanges.py +4 -5
- intentkit/skills/cryptocompare/fetch_top_market_cap.py +4 -5
- intentkit/skills/cryptocompare/fetch_top_volume.py +4 -5
- intentkit/skills/cryptocompare/fetch_trading_signals.py +5 -6
- intentkit/skills/cryptocompare/schema.json +3 -3
- intentkit/skills/cryptopanic/__init__.py +7 -10
- intentkit/skills/cryptopanic/base.py +51 -55
- intentkit/skills/cryptopanic/fetch_crypto_news.py +4 -8
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +5 -7
- intentkit/skills/cryptopanic/schema.json +105 -103
- intentkit/skills/dapplooker/__init__.py +2 -9
- intentkit/skills/dapplooker/base.py +4 -9
- intentkit/skills/dapplooker/dapplooker_token_data.py +7 -7
- intentkit/skills/dapplooker/schema.json +3 -5
- intentkit/skills/defillama/__init__.py +24 -74
- intentkit/skills/defillama/api.py +6 -9
- intentkit/skills/defillama/base.py +8 -19
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +8 -10
- intentkit/skills/defillama/coins/fetch_block.py +6 -8
- intentkit/skills/defillama/coins/fetch_current_prices.py +8 -10
- intentkit/skills/defillama/coins/fetch_first_price.py +7 -9
- intentkit/skills/defillama/coins/fetch_historical_prices.py +9 -11
- intentkit/skills/defillama/coins/fetch_price_chart.py +9 -11
- intentkit/skills/defillama/coins/fetch_price_percentage.py +7 -9
- intentkit/skills/defillama/config/chains.py +1 -3
- intentkit/skills/defillama/fees/fetch_fees_overview.py +24 -26
- intentkit/skills/defillama/schema.json +5 -1
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +16 -18
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +8 -10
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +5 -7
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +7 -9
- intentkit/skills/defillama/tests/api_integration.test.py +1 -1
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +4 -6
- intentkit/skills/defillama/tvl/fetch_chains.py +9 -11
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +4 -6
- intentkit/skills/defillama/tvl/fetch_protocol.py +32 -38
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +3 -5
- intentkit/skills/defillama/tvl/fetch_protocols.py +37 -45
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +42 -48
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +35 -37
- intentkit/skills/defillama/volumes/fetch_options_overview.py +24 -28
- intentkit/skills/defillama/yields/fetch_pool_chart.py +10 -12
- intentkit/skills/defillama/yields/fetch_pools.py +26 -30
- intentkit/skills/dexscreener/__init__.py +97 -102
- intentkit/skills/dexscreener/base.py +125 -130
- intentkit/skills/dexscreener/get_pair_info.py +4 -5
- intentkit/skills/dexscreener/get_token_pairs.py +4 -5
- intentkit/skills/dexscreener/get_tokens_info.py +7 -8
- intentkit/skills/dexscreener/model/search_token_response.py +80 -82
- intentkit/skills/dexscreener/schema.json +91 -93
- intentkit/skills/dexscreener/search_token.py +182 -184
- intentkit/skills/dexscreener/utils.py +15 -14
- intentkit/skills/dune_analytics/__init__.py +7 -9
- intentkit/skills/dune_analytics/base.py +48 -52
- intentkit/skills/dune_analytics/fetch_kol_buys.py +5 -7
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +6 -8
- intentkit/skills/dune_analytics/schema.json +104 -99
- intentkit/skills/elfa/__init__.py +5 -18
- intentkit/skills/elfa/base.py +10 -14
- intentkit/skills/elfa/mention.py +19 -21
- intentkit/skills/elfa/schema.json +3 -2
- intentkit/skills/elfa/stats.py +4 -4
- intentkit/skills/elfa/tokens.py +12 -12
- intentkit/skills/elfa/utils.py +26 -28
- intentkit/skills/enso/__init__.py +11 -31
- intentkit/skills/enso/base.py +54 -35
- intentkit/skills/enso/best_yield.py +16 -24
- intentkit/skills/enso/networks.py +6 -11
- intentkit/skills/enso/prices.py +11 -13
- intentkit/skills/enso/route.py +34 -38
- intentkit/skills/enso/schema.json +3 -2
- intentkit/skills/enso/tokens.py +29 -38
- intentkit/skills/enso/wallet.py +76 -191
- intentkit/skills/erc20/__init__.py +50 -0
- intentkit/skills/erc20/base.py +11 -0
- intentkit/skills/erc20/erc20.svg +5 -0
- intentkit/skills/erc20/schema.json +74 -0
- intentkit/skills/erc721/__init__.py +53 -0
- intentkit/skills/erc721/base.py +11 -0
- intentkit/skills/erc721/erc721.svg +5 -0
- intentkit/skills/erc721/schema.json +90 -0
- intentkit/skills/firecrawl/__init__.py +5 -18
- intentkit/skills/firecrawl/base.py +4 -9
- intentkit/skills/firecrawl/clear.py +4 -8
- intentkit/skills/firecrawl/crawl.py +19 -19
- intentkit/skills/firecrawl/query.py +4 -3
- intentkit/skills/firecrawl/schema.json +2 -6
- intentkit/skills/firecrawl/scrape.py +17 -22
- intentkit/skills/firecrawl/utils.py +50 -42
- intentkit/skills/github/__init__.py +2 -7
- intentkit/skills/github/base.py +1 -7
- intentkit/skills/github/github_search.py +1 -2
- intentkit/skills/github/schema.json +3 -4
- intentkit/skills/heurist/__init__.py +8 -27
- intentkit/skills/heurist/base.py +4 -9
- intentkit/skills/heurist/image_generation_animagine_xl.py +13 -15
- intentkit/skills/heurist/image_generation_arthemy_comics.py +13 -15
- intentkit/skills/heurist/image_generation_arthemy_real.py +13 -15
- intentkit/skills/heurist/image_generation_braindance.py +13 -15
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +13 -15
- intentkit/skills/heurist/image_generation_flux_1_dev.py +13 -15
- intentkit/skills/heurist/image_generation_sdxl.py +13 -15
- intentkit/skills/heurist/schema.json +2 -2
- intentkit/skills/http/__init__.py +4 -15
- intentkit/skills/http/base.py +1 -7
- intentkit/skills/http/get.py +21 -16
- intentkit/skills/http/post.py +23 -18
- intentkit/skills/http/put.py +23 -18
- intentkit/skills/http/schema.json +4 -5
- intentkit/skills/lifi/__init__.py +8 -13
- intentkit/skills/lifi/base.py +3 -9
- intentkit/skills/lifi/schema.json +17 -8
- intentkit/skills/lifi/token_execute.py +150 -60
- intentkit/skills/lifi/token_quote.py +8 -10
- intentkit/skills/lifi/utils.py +104 -51
- intentkit/skills/moralis/__init__.py +6 -10
- intentkit/skills/moralis/api.py +6 -7
- intentkit/skills/moralis/base.py +5 -10
- intentkit/skills/moralis/fetch_chain_portfolio.py +10 -11
- intentkit/skills/moralis/fetch_nft_portfolio.py +22 -22
- intentkit/skills/moralis/fetch_solana_portfolio.py +11 -12
- intentkit/skills/moralis/fetch_wallet_portfolio.py +8 -9
- intentkit/skills/moralis/schema.json +7 -2
- intentkit/skills/morpho/__init__.py +52 -0
- intentkit/skills/morpho/base.py +11 -0
- intentkit/skills/morpho/morpho.svg +12 -0
- intentkit/skills/morpho/schema.json +73 -0
- intentkit/skills/nation/__init__.py +4 -9
- intentkit/skills/nation/base.py +5 -10
- intentkit/skills/nation/nft_check.py +3 -4
- intentkit/skills/nation/schema.json +4 -3
- intentkit/skills/onchain.py +30 -0
- intentkit/skills/openai/__init__.py +17 -18
- intentkit/skills/openai/base.py +10 -14
- intentkit/skills/openai/dalle_image_generation.py +4 -9
- intentkit/skills/openai/gpt_avatar_generator.py +102 -0
- intentkit/skills/openai/gpt_image_generation.py +5 -9
- intentkit/skills/openai/gpt_image_mini_generator.py +92 -0
- intentkit/skills/openai/gpt_image_to_image.py +5 -9
- intentkit/skills/openai/image_to_text.py +3 -7
- intentkit/skills/openai/schema.json +34 -3
- intentkit/skills/portfolio/__init__.py +11 -35
- intentkit/skills/portfolio/base.py +33 -19
- intentkit/skills/portfolio/schema.json +3 -5
- intentkit/skills/portfolio/token_balances.py +21 -21
- intentkit/skills/portfolio/wallet_approvals.py +17 -18
- intentkit/skills/portfolio/wallet_defi_positions.py +3 -3
- intentkit/skills/portfolio/wallet_history.py +31 -31
- intentkit/skills/portfolio/wallet_net_worth.py +13 -13
- intentkit/skills/portfolio/wallet_nfts.py +19 -19
- intentkit/skills/portfolio/wallet_profitability.py +18 -18
- intentkit/skills/portfolio/wallet_profitability_summary.py +5 -5
- intentkit/skills/portfolio/wallet_stats.py +3 -3
- intentkit/skills/portfolio/wallet_swaps.py +19 -19
- intentkit/skills/pyth/__init__.py +50 -0
- intentkit/skills/pyth/base.py +11 -0
- intentkit/skills/pyth/pyth.svg +6 -0
- intentkit/skills/pyth/schema.json +75 -0
- intentkit/skills/skills.toml +36 -0
- intentkit/skills/slack/__init__.py +5 -17
- intentkit/skills/slack/base.py +3 -9
- intentkit/skills/slack/get_channel.py +8 -8
- intentkit/skills/slack/get_message.py +9 -9
- intentkit/skills/slack/schedule_message.py +5 -5
- intentkit/skills/slack/schema.json +2 -2
- intentkit/skills/slack/send_message.py +3 -5
- intentkit/skills/supabase/__init__.py +7 -23
- intentkit/skills/supabase/base.py +1 -7
- intentkit/skills/supabase/delete_data.py +4 -4
- intentkit/skills/supabase/fetch_data.py +12 -12
- intentkit/skills/supabase/insert_data.py +4 -4
- intentkit/skills/supabase/invoke_function.py +6 -6
- intentkit/skills/supabase/schema.json +2 -3
- intentkit/skills/supabase/update_data.py +6 -6
- intentkit/skills/supabase/upsert_data.py +4 -4
- intentkit/skills/superfluid/__init__.py +53 -0
- intentkit/skills/superfluid/base.py +11 -0
- intentkit/skills/superfluid/schema.json +89 -0
- intentkit/skills/superfluid/superfluid.svg +6 -0
- intentkit/skills/system/__init__.py +7 -24
- intentkit/skills/system/add_autonomous_task.py +10 -12
- intentkit/skills/system/delete_autonomous_task.py +2 -2
- intentkit/skills/system/edit_autonomous_task.py +14 -18
- intentkit/skills/system/list_autonomous_tasks.py +3 -5
- intentkit/skills/system/read_agent_api_key.py +6 -4
- intentkit/skills/system/regenerate_agent_api_key.py +6 -4
- intentkit/skills/system/schema.json +6 -8
- intentkit/skills/tavily/__init__.py +3 -12
- intentkit/skills/tavily/base.py +4 -9
- intentkit/skills/tavily/schema.json +3 -5
- intentkit/skills/tavily/tavily_extract.py +2 -4
- intentkit/skills/tavily/tavily_search.py +4 -6
- intentkit/skills/token/__init__.py +5 -10
- intentkit/skills/token/base.py +7 -11
- intentkit/skills/token/erc20_transfers.py +19 -19
- intentkit/skills/token/schema.json +3 -6
- intentkit/skills/token/token_analytics.py +3 -3
- intentkit/skills/token/token_price.py +13 -13
- intentkit/skills/token/token_search.py +9 -9
- intentkit/skills/twitter/__init__.py +11 -35
- intentkit/skills/twitter/base.py +22 -34
- intentkit/skills/twitter/follow_user.py +2 -6
- intentkit/skills/twitter/get_mentions.py +5 -12
- intentkit/skills/twitter/get_timeline.py +4 -12
- intentkit/skills/twitter/get_user_by_username.py +2 -6
- intentkit/skills/twitter/get_user_tweets.py +5 -13
- intentkit/skills/twitter/like_tweet.py +2 -6
- intentkit/skills/twitter/post_tweet.py +6 -9
- intentkit/skills/twitter/reply_tweet.py +6 -9
- intentkit/skills/twitter/retweet.py +2 -6
- intentkit/skills/twitter/schema.json +1 -0
- intentkit/skills/twitter/search_tweets.py +4 -12
- intentkit/skills/unrealspeech/__init__.py +2 -7
- intentkit/skills/unrealspeech/base.py +2 -8
- intentkit/skills/unrealspeech/schema.json +2 -5
- intentkit/skills/unrealspeech/text_to_speech.py +8 -8
- intentkit/skills/venice_audio/__init__.py +98 -106
- intentkit/skills/venice_audio/base.py +117 -121
- intentkit/skills/venice_audio/input.py +41 -41
- intentkit/skills/venice_audio/schema.json +151 -152
- intentkit/skills/venice_audio/venice_audio.py +38 -21
- intentkit/skills/venice_image/__init__.py +147 -154
- intentkit/skills/venice_image/api.py +138 -138
- intentkit/skills/venice_image/base.py +185 -192
- intentkit/skills/venice_image/config.py +33 -35
- intentkit/skills/venice_image/image_enhance/image_enhance.py +2 -3
- intentkit/skills/venice_image/image_enhance/image_enhance_base.py +21 -23
- intentkit/skills/venice_image/image_enhance/image_enhance_input.py +38 -40
- intentkit/skills/venice_image/image_generation/image_generation_base.py +11 -10
- intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -27
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -158
- intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -28
- intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -28
- intentkit/skills/venice_image/image_upscale/image_upscale.py +3 -3
- intentkit/skills/venice_image/image_upscale/image_upscale_base.py +21 -23
- intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -22
- intentkit/skills/venice_image/image_vision/image_vision.py +2 -2
- intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -17
- intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -9
- intentkit/skills/venice_image/schema.json +267 -267
- intentkit/skills/venice_image/utils.py +77 -78
- intentkit/skills/web_scraper/__init__.py +5 -18
- intentkit/skills/web_scraper/base.py +21 -7
- intentkit/skills/web_scraper/document_indexer.py +7 -6
- intentkit/skills/web_scraper/schema.json +2 -6
- intentkit/skills/web_scraper/scrape_and_index.py +15 -15
- intentkit/skills/web_scraper/utils.py +62 -63
- intentkit/skills/web_scraper/website_indexer.py +17 -19
- intentkit/skills/weth/__init__.py +49 -0
- intentkit/skills/weth/base.py +11 -0
- intentkit/skills/weth/schema.json +58 -0
- intentkit/skills/weth/weth.svg +6 -0
- intentkit/skills/wow/__init__.py +51 -0
- intentkit/skills/wow/base.py +11 -0
- intentkit/skills/wow/schema.json +89 -0
- intentkit/skills/wow/wow.svg +7 -0
- intentkit/skills/x402/__init__.py +58 -0
- intentkit/skills/x402/base.py +99 -0
- intentkit/skills/x402/http_request.py +117 -0
- intentkit/skills/x402/schema.json +40 -0
- intentkit/skills/x402/x402.webp +0 -0
- intentkit/skills/xmtp/__init__.py +4 -15
- intentkit/skills/xmtp/base.py +5 -5
- intentkit/skills/xmtp/price.py +7 -6
- intentkit/skills/xmtp/schema.json +69 -71
- intentkit/skills/xmtp/swap.py +6 -8
- intentkit/skills/xmtp/transfer.py +4 -6
- intentkit/utils/__init__.py +4 -0
- intentkit/utils/chain.py +198 -96
- intentkit/utils/ens.py +135 -0
- intentkit/utils/error.py +5 -2
- intentkit/utils/logging.py +9 -11
- intentkit/utils/schema.py +100 -0
- intentkit/utils/slack_alert.py +8 -8
- intentkit/utils/tx.py +16 -8
- intentkit/uv.lock +3377 -0
- {intentkit-0.7.5.dev3.dist-info → intentkit-0.8.34.dev7.dist-info}/METADATA +13 -15
- intentkit-0.8.34.dev7.dist-info/RECORD +478 -0
- intentkit-0.8.34.dev7.dist-info/licenses/LICENSE +21 -0
- intentkit/core/node.py +0 -215
- intentkit/models/conversation.py +0 -286
- intentkit/models/generator.py +0 -347
- intentkit/skills/cdp/get_balance.py +0 -110
- intentkit/skills/cdp/swap.py +0 -121
- intentkit/skills/moralis/tests/__init__.py +0 -0
- intentkit/skills/moralis/tests/test_wallet.py +0 -511
- intentkit-0.7.5.dev3.dist-info/RECORD +0 -424
- {intentkit-0.7.5.dev3.dist-info/licenses → intentkit}/LICENSE +0 -0
- {intentkit-0.7.5.dev3.dist-info → intentkit-0.8.34.dev7.dist-info}/WHEEL +0 -0
intentkit/models/redis.py
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
"""Redis client module for IntentKit."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
from redis.asyncio import Redis
|
|
7
6
|
|
|
8
7
|
logger = logging.getLogger(__name__)
|
|
9
8
|
|
|
10
9
|
# Global Redis client instance
|
|
11
|
-
_redis_client:
|
|
10
|
+
_redis_client: Redis | None = None
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
async def init_redis(
|
|
15
14
|
host: str,
|
|
16
15
|
port: int = 6379,
|
|
17
16
|
db: int = 0,
|
|
18
|
-
password:
|
|
17
|
+
password: str | None = None,
|
|
19
18
|
ssl: bool = False,
|
|
20
19
|
encoding: str = "utf-8",
|
|
21
20
|
decode_responses: bool = True,
|
|
@@ -74,6 +73,9 @@ def get_redis() -> Redis:
|
|
|
74
73
|
return _redis_client
|
|
75
74
|
|
|
76
75
|
|
|
76
|
+
DEFAULT_HEARTBEAT_TTL = 16 * 60
|
|
77
|
+
|
|
78
|
+
|
|
77
79
|
async def send_heartbeat(redis_client: Redis, name: str) -> None:
|
|
78
80
|
"""Set a heartbeat key in Redis that expires after 16 minutes.
|
|
79
81
|
|
|
@@ -83,7 +85,7 @@ async def send_heartbeat(redis_client: Redis, name: str) -> None:
|
|
|
83
85
|
"""
|
|
84
86
|
try:
|
|
85
87
|
key = f"intentkit:heartbeat:{name}"
|
|
86
|
-
await redis_client.set(key, 1, ex=
|
|
88
|
+
await redis_client.set(key, 1, ex=DEFAULT_HEARTBEAT_TTL)
|
|
87
89
|
except Exception as e:
|
|
88
90
|
logger.error(f"Failed to send heartbeat for {name}: {e}")
|
|
89
91
|
|
intentkit/models/skill.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import csv
|
|
1
4
|
import json
|
|
2
|
-
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import UTC, datetime
|
|
3
7
|
from decimal import Decimal
|
|
4
|
-
from
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Annotated, Any
|
|
5
10
|
|
|
6
|
-
from intentkit.models.base import Base
|
|
7
|
-
from intentkit.models.db import get_session
|
|
8
|
-
from intentkit.models.redis import get_redis
|
|
9
11
|
from pydantic import BaseModel, ConfigDict, Field
|
|
10
12
|
from sqlalchemy import (
|
|
11
13
|
Boolean,
|
|
12
|
-
Column,
|
|
13
14
|
DateTime,
|
|
14
15
|
Integer,
|
|
15
16
|
Numeric,
|
|
@@ -19,6 +20,14 @@ from sqlalchemy import (
|
|
|
19
20
|
select,
|
|
20
21
|
)
|
|
21
22
|
from sqlalchemy.dialects.postgresql import JSON, JSONB
|
|
23
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
24
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
25
|
+
|
|
26
|
+
from intentkit.models.base import Base
|
|
27
|
+
from intentkit.models.db import get_session
|
|
28
|
+
from intentkit.models.redis import get_redis
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
22
31
|
|
|
23
32
|
|
|
24
33
|
class AgentSkillDataTable(Base):
|
|
@@ -26,21 +35,23 @@ class AgentSkillDataTable(Base):
|
|
|
26
35
|
|
|
27
36
|
__tablename__ = "agent_skill_data"
|
|
28
37
|
|
|
29
|
-
agent_id =
|
|
30
|
-
skill =
|
|
31
|
-
key =
|
|
32
|
-
data
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
agent_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
39
|
+
skill: Mapped[str] = mapped_column(String, primary_key=True)
|
|
40
|
+
key: Mapped[str] = mapped_column(String, primary_key=True)
|
|
41
|
+
data: Mapped[dict[str, Any] | None] = mapped_column(
|
|
42
|
+
JSON().with_variant(JSONB(), "postgresql"), nullable=True
|
|
43
|
+
)
|
|
44
|
+
size: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
45
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
35
46
|
DateTime(timezone=True),
|
|
36
47
|
nullable=False,
|
|
37
48
|
server_default=func.now(),
|
|
38
49
|
)
|
|
39
|
-
updated_at =
|
|
50
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
40
51
|
DateTime(timezone=True),
|
|
41
52
|
nullable=False,
|
|
42
53
|
server_default=func.now(),
|
|
43
|
-
onupdate=lambda: datetime.now(
|
|
54
|
+
onupdate=lambda: datetime.now(UTC),
|
|
44
55
|
)
|
|
45
56
|
|
|
46
57
|
|
|
@@ -52,7 +63,7 @@ class AgentSkillDataCreate(BaseModel):
|
|
|
52
63
|
agent_id: Annotated[str, Field(description="ID of the agent this data belongs to")]
|
|
53
64
|
skill: Annotated[str, Field(description="Name of the skill this data is for")]
|
|
54
65
|
key: Annotated[str, Field(description="Key for this specific piece of data")]
|
|
55
|
-
data: Annotated[
|
|
66
|
+
data: Annotated[dict[str, Any], Field(description="JSON data stored for this key")]
|
|
56
67
|
|
|
57
68
|
async def save(self) -> "AgentSkillData":
|
|
58
69
|
"""Save or update skill data.
|
|
@@ -151,7 +162,7 @@ class AgentSkillData(AgentSkillDataCreate):
|
|
|
151
162
|
return result or 0
|
|
152
163
|
|
|
153
164
|
@classmethod
|
|
154
|
-
async def get(cls, agent_id: str, skill: str, key: str) ->
|
|
165
|
+
async def get(cls, agent_id: str, skill: str, key: str) -> dict | None:
|
|
155
166
|
"""Get skill data for an agent.
|
|
156
167
|
|
|
157
168
|
Args:
|
|
@@ -207,54 +218,54 @@ class AgentSkillData(AgentSkillDataCreate):
|
|
|
207
218
|
await db.commit()
|
|
208
219
|
|
|
209
220
|
|
|
210
|
-
class
|
|
211
|
-
"""Database table model for storing skill-specific data for
|
|
221
|
+
class ChatSkillDataTable(Base):
|
|
222
|
+
"""Database table model for storing skill-specific data for chats."""
|
|
212
223
|
|
|
213
|
-
__tablename__ = "
|
|
224
|
+
__tablename__ = "chat_skill_data"
|
|
214
225
|
|
|
215
|
-
|
|
216
|
-
skill =
|
|
217
|
-
key =
|
|
218
|
-
agent_id =
|
|
219
|
-
data
|
|
220
|
-
|
|
226
|
+
chat_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
227
|
+
skill: Mapped[str] = mapped_column(String, primary_key=True)
|
|
228
|
+
key: Mapped[str] = mapped_column(String, primary_key=True)
|
|
229
|
+
agent_id: Mapped[str] = mapped_column(String, nullable=False)
|
|
230
|
+
data: Mapped[dict[str, Any] | None] = mapped_column(
|
|
231
|
+
JSON().with_variant(JSONB(), "postgresql"), nullable=True
|
|
232
|
+
)
|
|
233
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
221
234
|
DateTime(timezone=True),
|
|
222
235
|
nullable=False,
|
|
223
236
|
server_default=func.now(),
|
|
224
237
|
)
|
|
225
|
-
updated_at =
|
|
238
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
226
239
|
DateTime(timezone=True),
|
|
227
240
|
nullable=False,
|
|
228
241
|
server_default=func.now(),
|
|
229
|
-
onupdate=lambda: datetime.now(
|
|
242
|
+
onupdate=lambda: datetime.now(UTC),
|
|
230
243
|
)
|
|
231
244
|
|
|
232
245
|
|
|
233
|
-
class
|
|
234
|
-
"""Base model for creating
|
|
246
|
+
class ChatSkillDataCreate(BaseModel):
|
|
247
|
+
"""Base model for creating chat skill data records."""
|
|
235
248
|
|
|
236
249
|
model_config = ConfigDict(from_attributes=True)
|
|
237
250
|
|
|
238
|
-
|
|
239
|
-
str, Field(description="ID of the thread this data belongs to")
|
|
240
|
-
]
|
|
251
|
+
chat_id: Annotated[str, Field(description="ID of the chat this data belongs to")]
|
|
241
252
|
skill: Annotated[str, Field(description="Name of the skill this data is for")]
|
|
242
253
|
key: Annotated[str, Field(description="Key for this specific piece of data")]
|
|
243
|
-
agent_id: Annotated[str, Field(description="ID of the agent that owns this
|
|
244
|
-
data: Annotated[
|
|
254
|
+
agent_id: Annotated[str, Field(description="ID of the agent that owns this chat")]
|
|
255
|
+
data: Annotated[dict[str, Any], Field(description="JSON data stored for this key")]
|
|
245
256
|
|
|
246
|
-
async def save(self) -> "
|
|
257
|
+
async def save(self) -> "ChatSkillData":
|
|
247
258
|
"""Save or update skill data.
|
|
248
259
|
|
|
249
260
|
Returns:
|
|
250
|
-
|
|
261
|
+
ChatSkillData: The saved chat skill data instance
|
|
251
262
|
"""
|
|
252
263
|
async with get_session() as db:
|
|
253
264
|
record = await db.scalar(
|
|
254
|
-
select(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
265
|
+
select(ChatSkillDataTable).where(
|
|
266
|
+
ChatSkillDataTable.chat_id == self.chat_id,
|
|
267
|
+
ChatSkillDataTable.skill == self.skill,
|
|
268
|
+
ChatSkillDataTable.key == self.key,
|
|
258
269
|
)
|
|
259
270
|
)
|
|
260
271
|
|
|
@@ -264,18 +275,18 @@ class ThreadSkillDataCreate(BaseModel):
|
|
|
264
275
|
record.agent_id = self.agent_id
|
|
265
276
|
else:
|
|
266
277
|
# Create new record
|
|
267
|
-
record =
|
|
278
|
+
record = ChatSkillDataTable(**self.model_dump())
|
|
268
279
|
db.add(record)
|
|
269
280
|
await db.commit()
|
|
270
281
|
await db.refresh(record)
|
|
271
|
-
return
|
|
282
|
+
return ChatSkillData.model_validate(record)
|
|
272
283
|
|
|
273
284
|
|
|
274
|
-
class
|
|
275
|
-
"""Model for storing skill-specific data for
|
|
285
|
+
class ChatSkillData(ChatSkillDataCreate):
|
|
286
|
+
"""Model for storing skill-specific data for chats.
|
|
276
287
|
|
|
277
|
-
This model uses a composite primary key of (
|
|
278
|
-
skill-specific data for
|
|
288
|
+
This model uses a composite primary key of (chat_id, skill, key) to store
|
|
289
|
+
skill-specific data for chats in a flexible way. It also includes agent_id
|
|
279
290
|
as a required field for tracking ownership.
|
|
280
291
|
"""
|
|
281
292
|
|
|
@@ -292,11 +303,11 @@ class ThreadSkillData(ThreadSkillDataCreate):
|
|
|
292
303
|
]
|
|
293
304
|
|
|
294
305
|
@classmethod
|
|
295
|
-
async def get(cls,
|
|
296
|
-
"""Get skill data for a
|
|
306
|
+
async def get(cls, chat_id: str, skill: str, key: str) -> dict | None:
|
|
307
|
+
"""Get skill data for a chat.
|
|
297
308
|
|
|
298
309
|
Args:
|
|
299
|
-
|
|
310
|
+
chat_id: ID of the chat
|
|
300
311
|
skill: Name of the skill
|
|
301
312
|
key: Data key
|
|
302
313
|
|
|
@@ -305,10 +316,10 @@ class ThreadSkillData(ThreadSkillDataCreate):
|
|
|
305
316
|
"""
|
|
306
317
|
async with get_session() as db:
|
|
307
318
|
record = await db.scalar(
|
|
308
|
-
select(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
319
|
+
select(ChatSkillDataTable).where(
|
|
320
|
+
ChatSkillDataTable.chat_id == chat_id,
|
|
321
|
+
ChatSkillDataTable.skill == skill,
|
|
322
|
+
ChatSkillDataTable.key == key,
|
|
312
323
|
)
|
|
313
324
|
)
|
|
314
325
|
return record.data if record else None
|
|
@@ -317,63 +328,135 @@ class ThreadSkillData(ThreadSkillDataCreate):
|
|
|
317
328
|
async def clean_data(
|
|
318
329
|
cls,
|
|
319
330
|
agent_id: str,
|
|
320
|
-
|
|
331
|
+
chat_id: Annotated[
|
|
321
332
|
str,
|
|
322
333
|
Field(
|
|
323
334
|
default="",
|
|
324
|
-
description="Optional ID of the
|
|
335
|
+
description="Optional ID of the chat. If provided, only cleans data for that chat.",
|
|
325
336
|
),
|
|
326
337
|
],
|
|
327
338
|
):
|
|
328
|
-
"""Clean all skill data for a
|
|
339
|
+
"""Clean all skill data for a chat or agent.
|
|
329
340
|
|
|
330
341
|
Args:
|
|
331
342
|
agent_id: ID of the agent
|
|
332
|
-
|
|
333
|
-
|
|
343
|
+
chat_id: Optional ID of the chat. If provided, only cleans data for that chat.
|
|
344
|
+
If empty, cleans all data for the agent.
|
|
334
345
|
"""
|
|
335
346
|
async with get_session() as db:
|
|
336
|
-
if
|
|
347
|
+
if chat_id and chat_id != "":
|
|
337
348
|
await db.execute(
|
|
338
|
-
delete(
|
|
339
|
-
|
|
340
|
-
|
|
349
|
+
delete(ChatSkillDataTable).where(
|
|
350
|
+
ChatSkillDataTable.agent_id == agent_id,
|
|
351
|
+
ChatSkillDataTable.chat_id == chat_id,
|
|
341
352
|
)
|
|
342
353
|
)
|
|
343
354
|
else:
|
|
344
355
|
await db.execute(
|
|
345
|
-
delete(
|
|
346
|
-
|
|
356
|
+
delete(ChatSkillDataTable).where(
|
|
357
|
+
ChatSkillDataTable.agent_id == agent_id
|
|
347
358
|
)
|
|
348
359
|
)
|
|
349
360
|
await db.commit()
|
|
350
361
|
|
|
351
362
|
|
|
363
|
+
def _skill_parse_bool(value: str | None) -> bool:
|
|
364
|
+
if value is None:
|
|
365
|
+
return False
|
|
366
|
+
return value.strip().lower() in {"true", "1", "yes"}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _skill_parse_optional_int(value: str | None) -> int | None:
|
|
370
|
+
if value is None:
|
|
371
|
+
return None
|
|
372
|
+
value = value.strip()
|
|
373
|
+
return int(value) if value else None
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _skill_parse_decimal(value: str | None, default: str = "0") -> Decimal:
|
|
377
|
+
value = (value or "").strip()
|
|
378
|
+
if not value:
|
|
379
|
+
value = default
|
|
380
|
+
return Decimal(value)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _load_default_skills() -> tuple[dict[str, "Skill"], dict[tuple[str, str], "Skill"]]:
|
|
384
|
+
"""Load default skills from CSV into lookup maps."""
|
|
385
|
+
|
|
386
|
+
path = Path(__file__).with_name("skills.csv")
|
|
387
|
+
if not path.exists():
|
|
388
|
+
logger.warning("Default skills CSV not found at %s", path)
|
|
389
|
+
return {}, {}
|
|
390
|
+
|
|
391
|
+
by_name: dict[str, Skill] = {}
|
|
392
|
+
by_category_config: dict[tuple[str, str], Skill] = {}
|
|
393
|
+
|
|
394
|
+
with path.open(newline="", encoding="utf-8") as csvfile:
|
|
395
|
+
reader = csv.DictReader(csvfile)
|
|
396
|
+
for row in reader:
|
|
397
|
+
try:
|
|
398
|
+
timestamp = datetime.now(UTC)
|
|
399
|
+
price_default = row.get("price") or "1"
|
|
400
|
+
skill = Skill(
|
|
401
|
+
name=row["name"],
|
|
402
|
+
category=row["category"],
|
|
403
|
+
config_name=row.get("config_name") or None,
|
|
404
|
+
enabled=_skill_parse_bool(row.get("enabled")),
|
|
405
|
+
price_level=_skill_parse_optional_int(row.get("price_level")),
|
|
406
|
+
price=_skill_parse_decimal(row.get("price"), default="1"),
|
|
407
|
+
price_self_key=_skill_parse_decimal(
|
|
408
|
+
row.get("price_self_key"), default=price_default
|
|
409
|
+
),
|
|
410
|
+
rate_limit_count=_skill_parse_optional_int(
|
|
411
|
+
row.get("rate_limit_count")
|
|
412
|
+
),
|
|
413
|
+
rate_limit_minutes=_skill_parse_optional_int(
|
|
414
|
+
row.get("rate_limit_minutes")
|
|
415
|
+
),
|
|
416
|
+
author=row.get("author") or None,
|
|
417
|
+
created_at=timestamp,
|
|
418
|
+
updated_at=timestamp,
|
|
419
|
+
)
|
|
420
|
+
except Exception as exc:
|
|
421
|
+
logger.error(
|
|
422
|
+
"Failed to load default skill %s: %s", row.get("name"), exc
|
|
423
|
+
)
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
by_name[skill.name] = skill
|
|
427
|
+
if skill.config_name:
|
|
428
|
+
by_category_config[(skill.category, skill.config_name)] = skill
|
|
429
|
+
|
|
430
|
+
return by_name, by_category_config
|
|
431
|
+
|
|
432
|
+
|
|
352
433
|
class SkillTable(Base):
|
|
353
434
|
"""Database table model for Skill."""
|
|
354
435
|
|
|
355
436
|
__tablename__ = "skills"
|
|
356
437
|
|
|
357
|
-
name =
|
|
358
|
-
enabled =
|
|
359
|
-
category =
|
|
360
|
-
config_name =
|
|
361
|
-
price_level =
|
|
362
|
-
price =
|
|
363
|
-
price_self_key =
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
438
|
+
name: Mapped[str] = mapped_column(String, primary_key=True)
|
|
439
|
+
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
440
|
+
category: Mapped[str] = mapped_column(String, nullable=False)
|
|
441
|
+
config_name: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
442
|
+
price_level: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
443
|
+
price: Mapped[Decimal] = mapped_column(Numeric(22, 4), nullable=False, default=1)
|
|
444
|
+
price_self_key: Mapped[Decimal] = mapped_column(
|
|
445
|
+
Numeric(22, 4), nullable=False, default=1
|
|
446
|
+
)
|
|
447
|
+
rate_limit_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
448
|
+
rate_limit_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
449
|
+
author: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
450
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
368
451
|
DateTime(timezone=True),
|
|
369
452
|
nullable=False,
|
|
370
453
|
server_default=func.now(),
|
|
371
454
|
)
|
|
372
|
-
updated_at =
|
|
455
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
373
456
|
DateTime(timezone=True),
|
|
374
457
|
nullable=False,
|
|
375
458
|
server_default=func.now(),
|
|
376
|
-
onupdate=lambda: datetime.now(
|
|
459
|
+
onupdate=lambda: datetime.now(UTC),
|
|
377
460
|
)
|
|
378
461
|
|
|
379
462
|
|
|
@@ -390,10 +473,8 @@ class Skill(BaseModel):
|
|
|
390
473
|
name: Annotated[str, Field(description="Name of the skill")]
|
|
391
474
|
enabled: Annotated[bool, Field(description="Is this skill enabled?")]
|
|
392
475
|
category: Annotated[str, Field(description="Category of the skill")]
|
|
393
|
-
config_name: Annotated[
|
|
394
|
-
price_level: Annotated[
|
|
395
|
-
Optional[int], Field(description="Price level for this skill")
|
|
396
|
-
]
|
|
476
|
+
config_name: Annotated[str | None, Field(description="Config name of the skill")]
|
|
477
|
+
price_level: Annotated[int | None, Field(description="Price level for this skill")]
|
|
397
478
|
price: Annotated[
|
|
398
479
|
Decimal, Field(description="Price for this skill", default=Decimal("1"))
|
|
399
480
|
]
|
|
@@ -401,11 +482,9 @@ class Skill(BaseModel):
|
|
|
401
482
|
Decimal,
|
|
402
483
|
Field(description="Price for this skill with self key", default=Decimal("1")),
|
|
403
484
|
]
|
|
404
|
-
rate_limit_count: Annotated[
|
|
405
|
-
rate_limit_minutes: Annotated[
|
|
406
|
-
|
|
407
|
-
]
|
|
408
|
-
author: Annotated[Optional[str], Field(description="Author of the skill")]
|
|
485
|
+
rate_limit_count: Annotated[int | None, Field(description="Rate limit count")]
|
|
486
|
+
rate_limit_minutes: Annotated[int | None, Field(description="Rate limit minutes")]
|
|
487
|
+
author: Annotated[str | None, Field(description="Author of the skill")]
|
|
409
488
|
created_at: Annotated[
|
|
410
489
|
datetime, Field(description="Timestamp when this record was created")
|
|
411
490
|
]
|
|
@@ -414,7 +493,7 @@ class Skill(BaseModel):
|
|
|
414
493
|
]
|
|
415
494
|
|
|
416
495
|
@staticmethod
|
|
417
|
-
async def get(name: str) ->
|
|
496
|
+
async def get(name: str) -> Skill | None:
|
|
418
497
|
"""Get a skill by name with Redis caching.
|
|
419
498
|
|
|
420
499
|
The skill is cached in Redis for 3 minutes.
|
|
@@ -447,20 +526,23 @@ class Skill(BaseModel):
|
|
|
447
526
|
stmt = select(SkillTable).where(SkillTable.name == name)
|
|
448
527
|
skill = await session.scalar(stmt)
|
|
449
528
|
|
|
450
|
-
# If skill
|
|
451
|
-
if
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
skill_model = Skill.model_validate(skill)
|
|
529
|
+
# If skill exists in database, convert and cache it
|
|
530
|
+
if skill:
|
|
531
|
+
skill_model = Skill.model_validate(skill)
|
|
532
|
+
await redis.set(cache_key, skill_model.model_dump_json(), ex=cache_ttl)
|
|
533
|
+
return skill_model
|
|
456
534
|
|
|
457
|
-
|
|
535
|
+
# Fallback to default skills loaded from CSV
|
|
536
|
+
default_skill = DEFAULT_SKILLS_BY_NAME.get(name)
|
|
537
|
+
if default_skill:
|
|
538
|
+
skill_model = default_skill.model_copy(deep=True)
|
|
458
539
|
await redis.set(cache_key, skill_model.model_dump_json(), ex=cache_ttl)
|
|
459
|
-
|
|
460
540
|
return skill_model
|
|
461
541
|
|
|
542
|
+
return None
|
|
543
|
+
|
|
462
544
|
@staticmethod
|
|
463
|
-
async def get_by_config_name(category: str, config_name: str) ->
|
|
545
|
+
async def get_by_config_name(category: str, config_name: str) -> "Skill" | None:
|
|
464
546
|
"""Get a skill by category and config_name.
|
|
465
547
|
|
|
466
548
|
Args:
|
|
@@ -477,9 +559,48 @@ class Skill(BaseModel):
|
|
|
477
559
|
)
|
|
478
560
|
skill = await session.scalar(stmt)
|
|
479
561
|
|
|
480
|
-
# If skill
|
|
481
|
-
if
|
|
482
|
-
return
|
|
562
|
+
# If skill exists in database, return it
|
|
563
|
+
if skill:
|
|
564
|
+
return Skill.model_validate(skill)
|
|
565
|
+
|
|
566
|
+
# Fallback to default skills loaded from CSV
|
|
567
|
+
default_skill = DEFAULT_SKILLS_BY_CATEGORY_CONFIG.get((category, config_name))
|
|
568
|
+
if default_skill:
|
|
569
|
+
return default_skill.model_copy(deep=True)
|
|
570
|
+
|
|
571
|
+
return None
|
|
572
|
+
|
|
573
|
+
@classmethod
|
|
574
|
+
async def get_all(cls, session: AsyncSession | None = None) -> list["Skill"]:
|
|
575
|
+
"""Return all skills merged from defaults and database overrides."""
|
|
576
|
+
|
|
577
|
+
if session is None:
|
|
578
|
+
async with get_session() as db:
|
|
579
|
+
return await cls.get_all(session=db)
|
|
580
|
+
|
|
581
|
+
skills: dict[str, Skill] = {
|
|
582
|
+
name: skill.model_copy(deep=True)
|
|
583
|
+
for name, skill in DEFAULT_SKILLS_BY_NAME.items()
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
result = await session.execute(select(SkillTable))
|
|
587
|
+
for row in result.scalars():
|
|
588
|
+
skill_model = cls.model_validate(row)
|
|
589
|
+
|
|
590
|
+
default_skill = DEFAULT_SKILLS_BY_NAME.get(skill_model.name)
|
|
591
|
+
if default_skill is not None:
|
|
592
|
+
# Merge database overrides with default skill configuration while
|
|
593
|
+
# keeping default values for fields that are omitted in the
|
|
594
|
+
# database (e.g. config_name).
|
|
595
|
+
skill_model = default_skill.model_copy(
|
|
596
|
+
update=skill_model.model_dump(exclude_none=True),
|
|
597
|
+
deep=True,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
skills[skill_model.name] = skill_model
|
|
601
|
+
|
|
602
|
+
return list(skills.values())
|
|
603
|
+
|
|
483
604
|
|
|
484
|
-
|
|
485
|
-
|
|
605
|
+
# Default skills loaded from CSV
|
|
606
|
+
DEFAULT_SKILLS_BY_NAME, DEFAULT_SKILLS_BY_CATEGORY_CONFIG = _load_default_skills()
|