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/clients/cdp.py
CHANGED
|
@@ -1,179 +1,155 @@
|
|
|
1
|
-
import
|
|
1
|
+
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from coinbase_agentkit import (
|
|
9
|
-
CdpEvmServerWalletProvider,
|
|
10
|
-
CdpEvmServerWalletProviderConfig,
|
|
3
|
+
|
|
4
|
+
from cdp import CdpClient, EvmServerAccount # noqa: E402
|
|
5
|
+
from coinbase_agentkit import ( # noqa: E402
|
|
6
|
+
CdpEvmWalletProvider,
|
|
7
|
+
CdpEvmWalletProviderConfig,
|
|
11
8
|
)
|
|
12
|
-
from eth_keys.datatypes import PrivateKey
|
|
13
|
-
from eth_utils import to_checksum_address
|
|
14
9
|
|
|
15
|
-
from intentkit.
|
|
16
|
-
from intentkit.models.agent import Agent
|
|
10
|
+
from intentkit.config.config import config
|
|
11
|
+
from intentkit.models.agent import Agent, AgentTable # noqa: E402
|
|
17
12
|
from intentkit.models.agent_data import AgentData
|
|
13
|
+
from intentkit.models.db import get_session
|
|
14
|
+
from intentkit.utils.error import IntentKitAPIError # noqa: E402
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
_wallet_providers: dict[str, tuple[str, str, CdpEvmWalletProvider]] = {}
|
|
17
|
+
_cdp_client: CdpClient | None = None
|
|
21
18
|
|
|
22
19
|
logger = logging.getLogger(__name__)
|
|
23
20
|
|
|
24
21
|
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
def get_cdp_client() -> CdpClient:
|
|
23
|
+
global _cdp_client
|
|
24
|
+
if _cdp_client:
|
|
25
|
+
return _cdp_client
|
|
26
|
+
|
|
27
|
+
# Get credentials from global configuration
|
|
28
|
+
api_key_id = config.cdp_api_key_id
|
|
29
|
+
api_key_secret = config.cdp_api_key_secret
|
|
30
|
+
wallet_secret = config.cdp_wallet_secret
|
|
31
|
+
|
|
32
|
+
_cdp_client = CdpClient(
|
|
33
|
+
api_key_id=api_key_id,
|
|
34
|
+
api_key_secret=api_key_secret,
|
|
35
|
+
wallet_secret=wallet_secret,
|
|
36
|
+
)
|
|
37
|
+
return _cdp_client
|
|
28
38
|
|
|
29
|
-
Args:
|
|
30
|
-
seed_hex: The BIP39 seed in hexadecimal format
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
def _assert_cdp_wallet_provider(agent: Agent) -> None:
|
|
41
|
+
if agent.wallet_provider != "cdp":
|
|
42
|
+
raise IntentKitAPIError(
|
|
43
|
+
400,
|
|
44
|
+
"BadWalletProvider",
|
|
45
|
+
"Your agent wallet provider is not cdp but you selected a skill that requires a cdp wallet.",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def _ensure_evm_account(
|
|
50
|
+
agent: Agent, agent_data: AgentData | None = None
|
|
51
|
+
) -> tuple[EvmServerAccount, AgentData]:
|
|
52
|
+
cdp_client = get_cdp_client()
|
|
53
|
+
agent_data = agent_data or await AgentData.get(agent.id)
|
|
54
|
+
address = agent_data.evm_wallet_address
|
|
55
|
+
account: EvmServerAccount | None = None
|
|
56
|
+
|
|
57
|
+
if not address:
|
|
58
|
+
logger.info("Creating new wallet...")
|
|
59
|
+
account = await cdp_client.evm.create_account(
|
|
60
|
+
name=agent.id,
|
|
61
|
+
)
|
|
62
|
+
address = account.address
|
|
63
|
+
logger.info("Created new wallet: %s", address)
|
|
64
|
+
|
|
65
|
+
agent_data.evm_wallet_address = address
|
|
66
|
+
await agent_data.save()
|
|
67
|
+
if not agent.slug:
|
|
68
|
+
async with get_session() as db:
|
|
69
|
+
db_agent = await db.get(AgentTable, agent.id)
|
|
70
|
+
if db_agent and not db_agent.slug:
|
|
71
|
+
db_agent.slug = agent_data.evm_wallet_address
|
|
72
|
+
await db.commit()
|
|
37
73
|
|
|
38
|
-
|
|
39
|
-
|
|
74
|
+
if account is None:
|
|
75
|
+
account = await cdp_client.evm.get_account(address=address)
|
|
40
76
|
|
|
41
|
-
|
|
42
|
-
private_key_bytes = bip32.get_privkey_from_path("m/44'/60'/0'/0/0")
|
|
77
|
+
return account, agent_data
|
|
43
78
|
|
|
44
|
-
# Create a private key object
|
|
45
|
-
private_key = PrivateKey(private_key_bytes)
|
|
46
79
|
|
|
47
|
-
|
|
48
|
-
|
|
80
|
+
async def get_evm_account(agent: Agent) -> EvmServerAccount:
|
|
81
|
+
_assert_cdp_wallet_provider(agent)
|
|
82
|
+
account, _ = await _ensure_evm_account(agent)
|
|
83
|
+
return account
|
|
49
84
|
|
|
50
|
-
# Get the Ethereum address
|
|
51
|
-
address = public_key.to_address()
|
|
52
85
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
86
|
+
def get_cdp_network(agent: Agent) -> str:
|
|
87
|
+
if not agent.network_id:
|
|
88
|
+
raise IntentKitAPIError(
|
|
89
|
+
400,
|
|
90
|
+
"BadNetworkID",
|
|
91
|
+
"Your agent network ID is not set. Please set it in the agent config.",
|
|
92
|
+
)
|
|
93
|
+
mapping = {
|
|
94
|
+
"ethereum-mainnet": "ethereum",
|
|
95
|
+
"base-mainnet": "base",
|
|
96
|
+
"arbitrum-mainnet": "arbitrum",
|
|
97
|
+
"optimism-mainnet": "optimism",
|
|
98
|
+
"polygon-mainnet": "polygon",
|
|
99
|
+
"base-sepolia": "base-sepolia",
|
|
57
100
|
}
|
|
101
|
+
if agent.network_id == "solana":
|
|
102
|
+
raise IntentKitAPIError(
|
|
103
|
+
400, "BadNetworkID", "Solana is not supported by CDP EVM."
|
|
104
|
+
)
|
|
105
|
+
cdp_network = mapping.get(agent.network_id)
|
|
106
|
+
if not cdp_network:
|
|
107
|
+
raise IntentKitAPIError(
|
|
108
|
+
400, "BadNetworkID", f"Unsupported network ID: {agent.network_id}"
|
|
109
|
+
)
|
|
110
|
+
return cdp_network
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def get_wallet_provider(agent: Agent) -> CdpEvmWalletProvider:
|
|
114
|
+
_assert_cdp_wallet_provider(agent)
|
|
115
|
+
if not agent.network_id:
|
|
116
|
+
raise IntentKitAPIError(
|
|
117
|
+
400,
|
|
118
|
+
"BadNetworkID",
|
|
119
|
+
"Your agent network ID is not set. Please set it in the agent config.",
|
|
120
|
+
)
|
|
58
121
|
|
|
122
|
+
agent_data = await AgentData.get(agent.id)
|
|
123
|
+
address = agent_data.evm_wallet_address
|
|
59
124
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
125
|
+
cache_entry = _wallet_providers.get(agent.id)
|
|
126
|
+
if cache_entry:
|
|
127
|
+
cached_network_id, cached_address, provider = cache_entry
|
|
128
|
+
if cached_network_id == agent.network_id:
|
|
129
|
+
if not address:
|
|
130
|
+
address = cached_address or provider.get_address()
|
|
131
|
+
if cached_address == address:
|
|
132
|
+
return provider
|
|
133
|
+
|
|
134
|
+
account, agent_data = await _ensure_evm_account(agent, agent_data)
|
|
135
|
+
address = account.address
|
|
136
|
+
|
|
137
|
+
# Get credentials from global config
|
|
138
|
+
api_key_id = config.cdp_api_key_id
|
|
139
|
+
api_key_secret = config.cdp_api_key_secret
|
|
140
|
+
wallet_secret = config.cdp_wallet_secret
|
|
64
141
|
|
|
65
|
-
|
|
66
|
-
api_key_id = skill_store.get_system_config("cdp_api_key_id")
|
|
67
|
-
api_key_secret = skill_store.get_system_config("cdp_api_key_secret")
|
|
68
|
-
wallet_secret = skill_store.get_system_config("cdp_wallet_secret")
|
|
142
|
+
network_id = agent.network_id
|
|
69
143
|
|
|
70
|
-
|
|
144
|
+
wallet_provider_config = CdpEvmWalletProviderConfig(
|
|
71
145
|
api_key_id=api_key_id,
|
|
72
146
|
api_key_secret=api_key_secret,
|
|
147
|
+
network_id=network_id,
|
|
148
|
+
address=address,
|
|
73
149
|
wallet_secret=wallet_secret,
|
|
74
150
|
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
self._agent_id = agent_id
|
|
81
|
-
self._skill_store = skill_store
|
|
82
|
-
self._wallet_provider: Optional[CdpEvmServerWalletProvider] = None
|
|
83
|
-
self._wallet_provider_config: Optional[CdpEvmServerWalletProviderConfig] = None
|
|
84
|
-
|
|
85
|
-
async def get_wallet_provider(self) -> CdpEvmServerWalletProvider:
|
|
86
|
-
if self._wallet_provider:
|
|
87
|
-
return self._wallet_provider
|
|
88
|
-
agent: Agent = await self._skill_store.get_agent_config(self._agent_id)
|
|
89
|
-
agent_data: AgentData = await self._skill_store.get_agent_data(self._agent_id)
|
|
90
|
-
network_id = agent.network_id
|
|
91
|
-
|
|
92
|
-
# Get credentials from skill store system config
|
|
93
|
-
api_key_id = self._skill_store.get_system_config("cdp_api_key_id")
|
|
94
|
-
api_key_secret = self._skill_store.get_system_config("cdp_api_key_secret")
|
|
95
|
-
wallet_secret = self._skill_store.get_system_config("cdp_wallet_secret")
|
|
96
|
-
|
|
97
|
-
# already have address
|
|
98
|
-
address = agent_data.evm_wallet_address
|
|
99
|
-
|
|
100
|
-
# new agent or address not migrated yet
|
|
101
|
-
if not address:
|
|
102
|
-
# create cdp client for later use
|
|
103
|
-
cdp_client = get_origin_cdp_client(self._skill_store)
|
|
104
|
-
# try migrating from v1 cdp_wallet_data
|
|
105
|
-
if agent_data.cdp_wallet_data:
|
|
106
|
-
wallet_data = json.loads(agent_data.cdp_wallet_data)
|
|
107
|
-
if not isinstance(wallet_data, dict):
|
|
108
|
-
raise ValueError("Invalid wallet data format")
|
|
109
|
-
if wallet_data.get("default_address_id") and wallet_data.get("seed"):
|
|
110
|
-
# verify seed and convert to pk
|
|
111
|
-
keys = bip39_seed_to_eth_keys(wallet_data["seed"])
|
|
112
|
-
if keys["address"] != wallet_data["default_address_id"]:
|
|
113
|
-
raise ValueError(
|
|
114
|
-
"Bad wallet data, seed does not match default_address_id"
|
|
115
|
-
)
|
|
116
|
-
# try to import wallet to v2
|
|
117
|
-
logger.info("Migrating wallet data to v2...")
|
|
118
|
-
await cdp_client.evm.import_account(
|
|
119
|
-
name=agent.id,
|
|
120
|
-
private_key=keys["private_key"],
|
|
121
|
-
)
|
|
122
|
-
address = keys["address"]
|
|
123
|
-
logger.info("Migrated wallet data to v2 successfully: %s", address)
|
|
124
|
-
# still not address
|
|
125
|
-
if not address:
|
|
126
|
-
logger.info("Creating new wallet...")
|
|
127
|
-
new_account = await cdp_client.evm.create_account(
|
|
128
|
-
name=agent.id,
|
|
129
|
-
)
|
|
130
|
-
address = new_account.address
|
|
131
|
-
logger.info("Created new wallet: %s", address)
|
|
132
|
-
|
|
133
|
-
# do not close cached global client
|
|
134
|
-
# now it should be created or migrated, store it
|
|
135
|
-
agent_data.evm_wallet_address = address
|
|
136
|
-
await agent_data.save()
|
|
137
|
-
|
|
138
|
-
# it must have v2 account now, load agentkit wallet provider
|
|
139
|
-
self._wallet_provider_config = CdpEvmServerWalletProviderConfig(
|
|
140
|
-
api_key_id=api_key_id,
|
|
141
|
-
api_key_secret=api_key_secret,
|
|
142
|
-
network_id=network_id,
|
|
143
|
-
address=address,
|
|
144
|
-
wallet_secret=wallet_secret,
|
|
145
|
-
)
|
|
146
|
-
self._wallet_provider = CdpEvmServerWalletProvider(self._wallet_provider_config)
|
|
147
|
-
# hack for cdp bug
|
|
148
|
-
if network_id == "base-mainnet":
|
|
149
|
-
self._wallet_provider._network.network_id = "base"
|
|
150
|
-
elif network_id == "arbitrum-mainnet":
|
|
151
|
-
self._wallet_provider._network.network_id = "arbitrum"
|
|
152
|
-
elif network_id == "optimism-mainnet":
|
|
153
|
-
self._wallet_provider._network.network_id = "optimism"
|
|
154
|
-
elif network_id == "polygon-mainnet":
|
|
155
|
-
self._wallet_provider._network.network_id = "polygon"
|
|
156
|
-
elif network_id == "ethereum-mainnet":
|
|
157
|
-
self._wallet_provider._network.network_id = "ethereum"
|
|
158
|
-
return self._wallet_provider
|
|
159
|
-
|
|
160
|
-
async def get_account(self) -> EvmServerAccount:
|
|
161
|
-
"""Get the account object from the wallet provider.
|
|
162
|
-
|
|
163
|
-
Returns:
|
|
164
|
-
EvmServerAccount: The account object that can be used for balance checks, transfers, etc.
|
|
165
|
-
"""
|
|
166
|
-
wallet_provider = await self.get_wallet_provider()
|
|
167
|
-
# Access the internal account object
|
|
168
|
-
return wallet_provider._account
|
|
169
|
-
|
|
170
|
-
async def get_provider_config(self) -> CdpEvmServerWalletProviderConfig:
|
|
171
|
-
if not self._wallet_provider_config:
|
|
172
|
-
await self.get_wallet_provider()
|
|
173
|
-
return self._wallet_provider_config
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
async def get_cdp_client(agent_id: str, skill_store: SkillStoreABC) -> "CdpClient":
|
|
177
|
-
if agent_id not in _clients:
|
|
178
|
-
_clients[agent_id] = CdpClient(agent_id, skill_store)
|
|
179
|
-
return _clients[agent_id]
|
|
151
|
+
wallet_provider = await asyncio.to_thread(
|
|
152
|
+
CdpEvmWalletProvider, wallet_provider_config
|
|
153
|
+
)
|
|
154
|
+
_wallet_providers[agent.id] = (network_id, address, wallet_provider)
|
|
155
|
+
return wallet_provider
|
|
@@ -5,7 +5,7 @@ S3 utility module for storing and retrieving images from AWS S3.
|
|
|
5
5
|
import logging
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from io import BytesIO
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import cast
|
|
9
9
|
|
|
10
10
|
import boto3
|
|
11
11
|
import filetype
|
|
@@ -13,40 +13,58 @@ import httpx
|
|
|
13
13
|
from botocore.exceptions import ClientError
|
|
14
14
|
from mypy_boto3_s3.client import S3Client
|
|
15
15
|
|
|
16
|
+
from intentkit.config.config import config
|
|
17
|
+
|
|
16
18
|
logger = logging.getLogger(__name__)
|
|
17
19
|
|
|
18
20
|
# Global variables for S3 configuration
|
|
19
|
-
_bucket:
|
|
20
|
-
_client:
|
|
21
|
-
_prefix:
|
|
22
|
-
_cdn_url:
|
|
21
|
+
_bucket: str | None = None
|
|
22
|
+
_client: S3Client | None = None
|
|
23
|
+
_prefix: str | None = None
|
|
24
|
+
_cdn_url: str | None = None
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
def
|
|
27
|
+
def get_s3_client() -> S3Client | None:
|
|
26
28
|
"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
bucket: S3 bucket name
|
|
31
|
-
cdn_url: CDN URL for the S3 bucket
|
|
32
|
-
env: Environment name for the prefix
|
|
33
|
-
|
|
34
|
-
Raises:
|
|
35
|
-
ValueError: If bucket or cdn_url is empty
|
|
29
|
+
Get or initialize S3 client and configuration.
|
|
30
|
+
Returns None if configuration is missing.
|
|
36
31
|
"""
|
|
37
32
|
global _bucket, _client, _prefix, _cdn_url
|
|
38
33
|
|
|
39
|
-
if not
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
if _client is not None:
|
|
35
|
+
return _client
|
|
36
|
+
|
|
37
|
+
if not config.aws_s3_bucket or not config.aws_s3_cdn_url:
|
|
38
|
+
# Only log once or if needed, but here we just return None
|
|
39
|
+
# The calling functions usually log "S3 not initialized"
|
|
40
|
+
return None
|
|
43
41
|
|
|
44
|
-
_bucket =
|
|
45
|
-
_cdn_url =
|
|
46
|
-
_prefix = f"{env}/intentkit/"
|
|
47
|
-
_client = boto3.client("s3")
|
|
42
|
+
_bucket = config.aws_s3_bucket
|
|
43
|
+
_cdn_url = config.aws_s3_cdn_url
|
|
44
|
+
_prefix = f"{config.env}/intentkit/"
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
try:
|
|
47
|
+
if config.aws_s3_endpoint_url:
|
|
48
|
+
_client = cast(
|
|
49
|
+
S3Client,
|
|
50
|
+
boto3.client(
|
|
51
|
+
"s3",
|
|
52
|
+
endpoint_url=config.aws_s3_endpoint_url,
|
|
53
|
+
region_name=config.aws_s3_region_name,
|
|
54
|
+
aws_access_key_id=config.aws_s3_access_key_id,
|
|
55
|
+
aws_secret_access_key=config.aws_s3_secret_access_key,
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
logger.info(
|
|
59
|
+
f"S3 initialized with custom endpoint: {config.aws_s3_endpoint_url}, bucket: {_bucket}, prefix: {_prefix}"
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
_client = cast(S3Client, boto3.client("s3"))
|
|
63
|
+
logger.info(f"S3 initialized with bucket: {_bucket}, prefix: {_prefix}")
|
|
64
|
+
return _client
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.error(f"Failed to initialize S3 client: {e}")
|
|
67
|
+
return None
|
|
50
68
|
|
|
51
69
|
|
|
52
70
|
async def store_image(url: str, key: str) -> str:
|
|
@@ -64,15 +82,16 @@ async def store_image(url: str, key: str) -> str:
|
|
|
64
82
|
ClientError: If the upload fails
|
|
65
83
|
httpx.HTTPError: If the download fails
|
|
66
84
|
"""
|
|
67
|
-
|
|
85
|
+
client = get_s3_client()
|
|
86
|
+
if not client or not _bucket or not _prefix or not _cdn_url:
|
|
68
87
|
# If S3 is not initialized, log and return the original URL
|
|
69
88
|
logger.info("S3 not initialized. Returning original URL.")
|
|
70
89
|
return url
|
|
71
90
|
|
|
72
91
|
try:
|
|
73
92
|
# Download the image from the URL asynchronously
|
|
74
|
-
async with httpx.AsyncClient() as
|
|
75
|
-
response = await
|
|
93
|
+
async with httpx.AsyncClient() as http_client:
|
|
94
|
+
response = await http_client.get(url, follow_redirects=True)
|
|
76
95
|
response.raise_for_status()
|
|
77
96
|
|
|
78
97
|
# Prepare the S3 key with prefix
|
|
@@ -93,7 +112,7 @@ async def store_image(url: str, key: str) -> str:
|
|
|
93
112
|
content_type = "image/jpeg"
|
|
94
113
|
|
|
95
114
|
# Upload to S3
|
|
96
|
-
|
|
115
|
+
client.upload_fileobj(
|
|
97
116
|
file_obj,
|
|
98
117
|
_bucket,
|
|
99
118
|
prefixed_key,
|
|
@@ -114,7 +133,7 @@ async def store_image(url: str, key: str) -> str:
|
|
|
114
133
|
|
|
115
134
|
|
|
116
135
|
async def store_image_bytes(
|
|
117
|
-
image_bytes: bytes, key: str, content_type:
|
|
136
|
+
image_bytes: bytes, key: str, content_type: str | None = None
|
|
118
137
|
) -> str:
|
|
119
138
|
"""
|
|
120
139
|
Store raw image bytes to S3.
|
|
@@ -131,7 +150,8 @@ async def store_image_bytes(
|
|
|
131
150
|
ClientError: If the upload fails
|
|
132
151
|
ValueError: If S3 is not initialized or image_bytes is empty
|
|
133
152
|
"""
|
|
134
|
-
|
|
153
|
+
client = get_s3_client()
|
|
154
|
+
if not client or not _bucket or not _prefix or not _cdn_url:
|
|
135
155
|
# If S3 is not initialized, log and return empty string
|
|
136
156
|
logger.info("S3 not initialized. Cannot store image bytes.")
|
|
137
157
|
return ""
|
|
@@ -158,7 +178,7 @@ async def store_image_bytes(
|
|
|
158
178
|
|
|
159
179
|
logger.info("uploading image to s3")
|
|
160
180
|
# Upload to S3
|
|
161
|
-
|
|
181
|
+
client.upload_fileobj(
|
|
162
182
|
file_obj,
|
|
163
183
|
_bucket,
|
|
164
184
|
prefixed_key,
|
|
@@ -182,11 +202,65 @@ class FileType(str, Enum):
|
|
|
182
202
|
PDF = "pdf"
|
|
183
203
|
|
|
184
204
|
|
|
205
|
+
async def store_file(
|
|
206
|
+
content: bytes,
|
|
207
|
+
key: str,
|
|
208
|
+
content_type: str | None = None,
|
|
209
|
+
size: int | None = None,
|
|
210
|
+
) -> str:
|
|
211
|
+
"""Store raw file bytes with automatic content type detection."""
|
|
212
|
+
client = get_s3_client()
|
|
213
|
+
if not client or not _bucket or not _prefix or not _cdn_url:
|
|
214
|
+
logger.info("S3 not initialized. Cannot store file bytes.")
|
|
215
|
+
return ""
|
|
216
|
+
|
|
217
|
+
if not content:
|
|
218
|
+
raise ValueError("File content cannot be empty")
|
|
219
|
+
|
|
220
|
+
actual_size = len(content)
|
|
221
|
+
if size is not None and size != actual_size:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"Provided size {size} does not match actual content size {actual_size} bytes"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
effective_size = size if size is not None else actual_size
|
|
227
|
+
|
|
228
|
+
detected_content_type = content_type
|
|
229
|
+
if not detected_content_type:
|
|
230
|
+
kind = filetype.guess(content)
|
|
231
|
+
detected_content_type = (
|
|
232
|
+
kind.mime if kind and kind.mime else "application/octet-stream"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
prefixed_key = f"{_prefix}{key}"
|
|
236
|
+
file_obj = BytesIO(content)
|
|
237
|
+
|
|
238
|
+
logger.info(
|
|
239
|
+
"Uploading file to S3 with content type %s and size %s bytes",
|
|
240
|
+
detected_content_type,
|
|
241
|
+
effective_size,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
client.upload_fileobj(
|
|
245
|
+
file_obj,
|
|
246
|
+
_bucket,
|
|
247
|
+
prefixed_key,
|
|
248
|
+
ExtraArgs={
|
|
249
|
+
"ContentType": detected_content_type,
|
|
250
|
+
"ContentDisposition": "inline",
|
|
251
|
+
},
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
cdn_url = f"{_cdn_url}/{prefixed_key}"
|
|
255
|
+
logger.info("File uploaded successfully to %s", cdn_url)
|
|
256
|
+
return cdn_url
|
|
257
|
+
|
|
258
|
+
|
|
185
259
|
async def store_file_bytes(
|
|
186
260
|
file_bytes: bytes,
|
|
187
261
|
key: str,
|
|
188
262
|
file_type: FileType,
|
|
189
|
-
size_limit_bytes:
|
|
263
|
+
size_limit_bytes: int | None = None,
|
|
190
264
|
) -> str:
|
|
191
265
|
"""
|
|
192
266
|
Store raw file bytes (image, video, sound, pdf) to S3.
|
|
@@ -204,7 +278,8 @@ async def store_file_bytes(
|
|
|
204
278
|
ClientError: If the upload fails
|
|
205
279
|
ValueError: If S3 is not initialized, file_bytes is empty, or file exceeds size limit
|
|
206
280
|
"""
|
|
207
|
-
|
|
281
|
+
client = get_s3_client()
|
|
282
|
+
if not client or not _bucket or not _prefix or not _cdn_url:
|
|
208
283
|
logger.info("S3 not initialized. Cannot store file bytes.")
|
|
209
284
|
return ""
|
|
210
285
|
if not file_bytes:
|
|
@@ -250,7 +325,7 @@ async def store_file_bytes(
|
|
|
250
325
|
logger.info(f"Uploading {file_type} to S3 with content type {content_type}")
|
|
251
326
|
|
|
252
327
|
# Upload to S3
|
|
253
|
-
|
|
328
|
+
client.upload_fileobj(
|
|
254
329
|
file_obj,
|
|
255
330
|
_bucket,
|
|
256
331
|
prefixed_key,
|