intentkit 0.8.12rc0__py3-none-any.whl → 0.8.13.dev2__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 +1 -1
- intentkit/abstracts/skill.py +2 -59
- intentkit/clients/__init__.py +3 -2
- intentkit/clients/cdp.py +63 -44
- intentkit/clients/twitter.py +35 -28
- intentkit/config/config.py +1 -0
- intentkit/core/agent.py +2 -279
- intentkit/core/asset.py +63 -16
- intentkit/core/engine.py +9 -5
- intentkit/core/scheduler.py +8 -8
- intentkit/models/agent.py +138 -94
- intentkit/models/agent_schema.json +6 -9
- intentkit/models/chat.py +1 -0
- intentkit/models/llm.csv +15 -12
- intentkit/models/skills.csv +5 -4
- intentkit/skills/acolyt/__init__.py +2 -9
- intentkit/skills/acolyt/base.py +2 -5
- intentkit/skills/aixbt/__init__.py +2 -13
- intentkit/skills/aixbt/base.py +0 -4
- intentkit/skills/aixbt/projects.py +1 -2
- intentkit/skills/allora/__init__.py +2 -9
- intentkit/skills/allora/base.py +2 -5
- intentkit/skills/base.py +101 -37
- intentkit/skills/basename/__init__.py +1 -3
- intentkit/skills/carv/__init__.py +116 -121
- intentkit/skills/carv/base.py +184 -185
- intentkit/skills/casino/__init__.py +4 -15
- intentkit/skills/casino/base.py +0 -4
- intentkit/skills/casino/deck_draw.py +1 -2
- intentkit/skills/casino/deck_shuffle.py +1 -2
- intentkit/skills/casino/dice_roll.py +1 -2
- intentkit/skills/cdp/__init__.py +0 -5
- intentkit/skills/cdp/base.py +0 -4
- intentkit/skills/cdp/schema.json +1 -17
- intentkit/skills/chainlist/__init__.py +2 -7
- intentkit/skills/chainlist/base.py +0 -4
- intentkit/skills/common/__init__.py +2 -9
- intentkit/skills/common/base.py +0 -4
- intentkit/skills/cookiefun/__init__.py +6 -9
- intentkit/skills/cookiefun/base.py +0 -4
- intentkit/skills/cryptocompare/__init__.py +7 -24
- intentkit/skills/cryptocompare/base.py +0 -5
- intentkit/skills/cryptopanic/__init__.py +3 -6
- intentkit/skills/cryptopanic/base.py +53 -55
- intentkit/skills/cryptopanic/fetch_crypto_news.py +0 -2
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +1 -3
- intentkit/skills/dapplooker/__init__.py +2 -9
- intentkit/skills/dapplooker/base.py +2 -5
- intentkit/skills/defillama/__init__.py +24 -74
- intentkit/skills/defillama/base.py +0 -4
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +2 -2
- intentkit/skills/defillama/coins/fetch_block.py +2 -2
- intentkit/skills/defillama/coins/fetch_current_prices.py +2 -2
- intentkit/skills/defillama/coins/fetch_first_price.py +2 -2
- intentkit/skills/defillama/coins/fetch_historical_prices.py +2 -2
- intentkit/skills/defillama/coins/fetch_price_chart.py +2 -2
- intentkit/skills/defillama/coins/fetch_price_percentage.py +2 -2
- intentkit/skills/defillama/fees/fetch_fees_overview.py +2 -2
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +2 -2
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +2 -2
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +2 -2
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +2 -2
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -2
- intentkit/skills/defillama/tvl/fetch_chains.py +2 -2
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -2
- intentkit/skills/defillama/tvl/fetch_protocol.py +2 -2
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +2 -2
- intentkit/skills/defillama/tvl/fetch_protocols.py +2 -2
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +2 -2
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +2 -2
- intentkit/skills/defillama/volumes/fetch_options_overview.py +2 -2
- intentkit/skills/defillama/yields/fetch_pool_chart.py +2 -2
- intentkit/skills/defillama/yields/fetch_pools.py +2 -2
- intentkit/skills/dexscreener/__init__.py +97 -102
- intentkit/skills/dexscreener/base.py +125 -130
- intentkit/skills/dexscreener/get_pair_info.py +2 -3
- intentkit/skills/dexscreener/get_token_pairs.py +2 -3
- intentkit/skills/dexscreener/get_tokens_info.py +2 -3
- intentkit/skills/dexscreener/search_token.py +2 -4
- intentkit/skills/dune_analytics/__init__.py +4 -6
- intentkit/skills/dune_analytics/base.py +50 -52
- intentkit/skills/dune_analytics/fetch_kol_buys.py +0 -2
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +0 -2
- intentkit/skills/elfa/__init__.py +5 -18
- intentkit/skills/elfa/base.py +8 -10
- intentkit/skills/enso/__init__.py +9 -29
- intentkit/skills/enso/base.py +3 -6
- intentkit/skills/enso/route.py +1 -3
- intentkit/skills/erc20/__init__.py +1 -5
- intentkit/skills/erc721/__init__.py +1 -3
- intentkit/skills/firecrawl/__init__.py +5 -18
- intentkit/skills/firecrawl/base.py +2 -5
- intentkit/skills/firecrawl/crawl.py +10 -9
- intentkit/skills/firecrawl/query.py +3 -1
- intentkit/skills/firecrawl/scrape.py +8 -10
- intentkit/skills/firecrawl/utils.py +25 -26
- intentkit/skills/github/__init__.py +2 -7
- intentkit/skills/github/base.py +0 -4
- intentkit/skills/heurist/__init__.py +8 -27
- intentkit/skills/heurist/base.py +2 -5
- intentkit/skills/heurist/image_generation_animagine_xl.py +5 -5
- intentkit/skills/heurist/image_generation_arthemy_comics.py +5 -5
- intentkit/skills/heurist/image_generation_arthemy_real.py +5 -5
- intentkit/skills/heurist/image_generation_braindance.py +5 -5
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +5 -5
- intentkit/skills/heurist/image_generation_flux_1_dev.py +5 -5
- intentkit/skills/heurist/image_generation_sdxl.py +5 -5
- intentkit/skills/http/__init__.py +4 -15
- intentkit/skills/http/base.py +0 -4
- intentkit/skills/lifi/__init__.py +1 -6
- intentkit/skills/lifi/base.py +0 -4
- intentkit/skills/lifi/token_execute.py +1 -4
- intentkit/skills/lifi/token_quote.py +1 -3
- intentkit/skills/moralis/__init__.py +3 -7
- intentkit/skills/moralis/base.py +2 -5
- intentkit/skills/morpho/__init__.py +1 -3
- intentkit/skills/nation/__init__.py +2 -7
- intentkit/skills/nation/base.py +4 -7
- intentkit/skills/onchain.py +27 -0
- intentkit/skills/openai/__init__.py +5 -18
- intentkit/skills/openai/base.py +8 -10
- intentkit/skills/openai/dalle_image_generation.py +2 -5
- intentkit/skills/openai/gpt_image_generation.py +2 -5
- intentkit/skills/openai/gpt_image_to_image.py +2 -5
- intentkit/skills/openai/image_to_text.py +2 -5
- intentkit/skills/portfolio/__init__.py +11 -35
- intentkit/skills/portfolio/base.py +2 -5
- intentkit/skills/pyth/__init__.py +1 -5
- intentkit/skills/skills.toml +4 -0
- intentkit/skills/slack/__init__.py +5 -17
- intentkit/skills/slack/base.py +0 -4
- intentkit/skills/supabase/__init__.py +7 -23
- intentkit/skills/supabase/base.py +0 -4
- intentkit/skills/superfluid/__init__.py +1 -3
- intentkit/skills/system/__init__.py +7 -24
- intentkit/skills/system/add_autonomous_task.py +2 -2
- intentkit/skills/system/delete_autonomous_task.py +2 -2
- intentkit/skills/system/edit_autonomous_task.py +2 -4
- intentkit/skills/system/list_autonomous_tasks.py +2 -2
- intentkit/skills/system/read_agent_api_key.py +6 -4
- intentkit/skills/system/regenerate_agent_api_key.py +6 -4
- intentkit/skills/tavily/__init__.py +3 -12
- intentkit/skills/tavily/base.py +2 -5
- intentkit/skills/tavily/tavily_extract.py +1 -2
- intentkit/skills/tavily/tavily_search.py +3 -3
- intentkit/skills/token/__init__.py +5 -10
- intentkit/skills/token/base.py +2 -6
- intentkit/skills/twitter/__init__.py +11 -35
- intentkit/skills/twitter/base.py +14 -16
- intentkit/skills/twitter/follow_user.py +0 -1
- intentkit/skills/twitter/get_mentions.py +0 -1
- intentkit/skills/twitter/get_timeline.py +0 -1
- intentkit/skills/twitter/get_user_by_username.py +0 -1
- intentkit/skills/twitter/get_user_tweets.py +0 -1
- intentkit/skills/twitter/like_tweet.py +0 -1
- intentkit/skills/twitter/post_tweet.py +2 -2
- intentkit/skills/twitter/reply_tweet.py +2 -2
- intentkit/skills/twitter/retweet.py +0 -1
- intentkit/skills/twitter/search_tweets.py +0 -1
- intentkit/skills/unrealspeech/__init__.py +2 -7
- intentkit/skills/unrealspeech/base.py +0 -4
- intentkit/skills/venice_audio/__init__.py +99 -106
- intentkit/skills/venice_audio/base.py +118 -121
- intentkit/skills/venice_audio/venice_audio.py +1 -5
- intentkit/skills/venice_image/__init__.py +147 -154
- intentkit/skills/venice_image/base.py +185 -192
- intentkit/skills/web_scraper/__init__.py +5 -18
- intentkit/skills/web_scraper/base.py +20 -4
- intentkit/skills/web_scraper/document_indexer.py +6 -4
- intentkit/skills/web_scraper/scrape_and_index.py +11 -8
- intentkit/skills/web_scraper/utils.py +31 -27
- intentkit/skills/web_scraper/website_indexer.py +7 -8
- intentkit/skills/weth/__init__.py +1 -5
- intentkit/skills/wow/__init__.py +1 -5
- intentkit/skills/x402/__init__.py +53 -0
- intentkit/skills/x402/ask_agent.py +82 -0
- intentkit/skills/x402/base.py +9 -0
- intentkit/skills/x402/schema.json +40 -0
- intentkit/skills/x402/x402.png +0 -0
- intentkit/skills/xmtp/__init__.py +4 -15
- intentkit/skills/xmtp/base.py +2 -2
- intentkit/skills/xmtp/price.py +3 -3
- intentkit/skills/xmtp/swap.py +4 -4
- intentkit/utils/schema.py +100 -0
- {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev2.dist-info}/METADATA +1 -1
- {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev2.dist-info}/RECORD +188 -181
- {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev2.dist-info}/WHEEL +0 -0
- {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dev2.dist-info}/licenses/LICENSE +0 -0
|
@@ -17,7 +17,7 @@ from langchain_core.documents import Document
|
|
|
17
17
|
from langchain_openai import OpenAIEmbeddings
|
|
18
18
|
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
|
19
19
|
|
|
20
|
-
from intentkit.
|
|
20
|
+
from intentkit.config.config import config
|
|
21
21
|
from intentkit.models.skill import AgentSkillData, AgentSkillDataCreate
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
@@ -63,12 +63,20 @@ METADATA_KEY_PREFIX = "indexed_urls"
|
|
|
63
63
|
class VectorStoreManager:
|
|
64
64
|
"""Manages vector store operations including creation, saving, loading, and merging."""
|
|
65
65
|
|
|
66
|
-
def __init__(self,
|
|
67
|
-
self.
|
|
66
|
+
def __init__(self, embedding_api_key: Optional[str] = None):
|
|
67
|
+
self._embedding_api_key = embedding_api_key
|
|
68
|
+
|
|
69
|
+
def _resolve_api_key(self) -> str:
|
|
70
|
+
"""Resolve the OpenAI API key to use for embeddings."""
|
|
71
|
+
if self._embedding_api_key:
|
|
72
|
+
return self._embedding_api_key
|
|
73
|
+
if config.openai_api_key:
|
|
74
|
+
return config.openai_api_key
|
|
75
|
+
raise ValueError("OpenAI API key is not configured")
|
|
68
76
|
|
|
69
77
|
def create_embeddings(self) -> OpenAIEmbeddings:
|
|
70
|
-
"""Create OpenAI embeddings using
|
|
71
|
-
api_key = self.
|
|
78
|
+
"""Create OpenAI embeddings using the resolved API key."""
|
|
79
|
+
api_key = self._resolve_api_key()
|
|
72
80
|
return OpenAIEmbeddings(api_key=api_key)
|
|
73
81
|
|
|
74
82
|
def get_storage_keys(self, agent_id: str) -> Tuple[str, str]:
|
|
@@ -226,7 +234,8 @@ class VectorStoreManager:
|
|
|
226
234
|
|
|
227
235
|
return total_size
|
|
228
236
|
|
|
229
|
-
|
|
237
|
+
@staticmethod
|
|
238
|
+
def format_size(size_bytes: int) -> str:
|
|
230
239
|
"""Format size in bytes to human readable format."""
|
|
231
240
|
if size_bytes < 1024:
|
|
232
241
|
return f"{size_bytes} B"
|
|
@@ -312,13 +321,12 @@ class DocumentProcessor:
|
|
|
312
321
|
class MetadataManager:
|
|
313
322
|
"""Manages metadata for indexed content."""
|
|
314
323
|
|
|
315
|
-
def __init__(self,
|
|
316
|
-
self.
|
|
324
|
+
def __init__(self, vector_manager: VectorStoreManager):
|
|
325
|
+
self._vector_manager = vector_manager
|
|
317
326
|
|
|
318
327
|
async def get_existing_metadata(self, agent_id: str) -> Dict:
|
|
319
328
|
"""Get existing metadata for an agent."""
|
|
320
|
-
|
|
321
|
-
_, metadata_key = vs_manager.get_storage_keys(agent_id)
|
|
329
|
+
_, metadata_key = self._vector_manager.get_storage_keys(agent_id)
|
|
322
330
|
return await AgentSkillData.get(agent_id, "web_scraper", metadata_key) or {}
|
|
323
331
|
|
|
324
332
|
def create_url_metadata(
|
|
@@ -376,8 +384,7 @@ class MetadataManager:
|
|
|
376
384
|
|
|
377
385
|
async def update_metadata(self, agent_id: str, new_metadata: Dict) -> None:
|
|
378
386
|
"""Update metadata for an agent."""
|
|
379
|
-
|
|
380
|
-
_, metadata_key = vs_manager.get_storage_keys(agent_id)
|
|
387
|
+
_, metadata_key = self._vector_manager.get_storage_keys(agent_id)
|
|
381
388
|
|
|
382
389
|
# Get existing metadata
|
|
383
390
|
existing_metadata = await self.get_existing_metadata(agent_id)
|
|
@@ -453,9 +460,8 @@ class ResponseFormatter:
|
|
|
453
460
|
|
|
454
461
|
# Add size information
|
|
455
462
|
if current_size_bytes > 0:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
max_size = vs_manager.format_size(MAX_CONTENT_SIZE_BYTES)
|
|
463
|
+
formatted_size = VectorStoreManager.format_size(current_size_bytes)
|
|
464
|
+
max_size = VectorStoreManager.format_size(MAX_CONTENT_SIZE_BYTES)
|
|
459
465
|
response_parts.append(
|
|
460
466
|
f"Current storage size: {formatted_size} / {max_size}"
|
|
461
467
|
)
|
|
@@ -477,7 +483,7 @@ class ResponseFormatter:
|
|
|
477
483
|
async def scrape_and_index_urls(
|
|
478
484
|
urls: List[str],
|
|
479
485
|
agent_id: str,
|
|
480
|
-
|
|
486
|
+
vector_manager: VectorStoreManager,
|
|
481
487
|
chunk_size: int = DEFAULT_CHUNK_SIZE,
|
|
482
488
|
chunk_overlap: int = DEFAULT_CHUNK_OVERLAP,
|
|
483
489
|
requests_per_second: int = DEFAULT_REQUESTS_PER_SECOND,
|
|
@@ -488,7 +494,7 @@ async def scrape_and_index_urls(
|
|
|
488
494
|
Args:
|
|
489
495
|
urls: List of URLs to scrape
|
|
490
496
|
agent_id: Agent identifier for storage
|
|
491
|
-
|
|
497
|
+
vector_manager: Manager for vector store operations
|
|
492
498
|
chunk_size: Size of text chunks
|
|
493
499
|
chunk_overlap: Overlap between chunks
|
|
494
500
|
requests_per_second: Rate limiting for requests
|
|
@@ -516,16 +522,15 @@ async def scrape_and_index_urls(
|
|
|
516
522
|
return 0, False, []
|
|
517
523
|
|
|
518
524
|
# Check existing content size
|
|
519
|
-
|
|
520
|
-
current_size = await vs_manager.get_content_size(agent_id)
|
|
525
|
+
current_size = await vector_manager.get_content_size(agent_id)
|
|
521
526
|
|
|
522
527
|
logger.info(
|
|
523
|
-
f"[{agent_id}] Current storage size: {
|
|
528
|
+
f"[{agent_id}] Current storage size: {VectorStoreManager.format_size(current_size)}"
|
|
524
529
|
)
|
|
525
530
|
|
|
526
531
|
if current_size >= MAX_CONTENT_SIZE_BYTES:
|
|
527
532
|
logger.warning(
|
|
528
|
-
f"[{agent_id}] Storage limit already reached: {
|
|
533
|
+
f"[{agent_id}] Storage limit already reached: {VectorStoreManager.format_size(current_size)}"
|
|
529
534
|
)
|
|
530
535
|
return 0, False, []
|
|
531
536
|
|
|
@@ -595,7 +600,7 @@ async def scrape_and_index_urls(
|
|
|
595
600
|
|
|
596
601
|
# Process and index this URL's content
|
|
597
602
|
chunks, merged = await index_documents(
|
|
598
|
-
documents, agent_id,
|
|
603
|
+
documents, agent_id, vector_manager, chunk_size, chunk_overlap
|
|
599
604
|
)
|
|
600
605
|
|
|
601
606
|
if chunks > 0:
|
|
@@ -605,7 +610,7 @@ async def scrape_and_index_urls(
|
|
|
605
610
|
current_size += content_size
|
|
606
611
|
|
|
607
612
|
logger.info(
|
|
608
|
-
f"[{agent_id}] Processed {url}: {chunks} chunks, current size: {
|
|
613
|
+
f"[{agent_id}] Processed {url}: {chunks} chunks, current size: {VectorStoreManager.format_size(current_size)}"
|
|
609
614
|
)
|
|
610
615
|
|
|
611
616
|
# Add delay for rate limiting
|
|
@@ -633,7 +638,7 @@ async def scrape_and_index_urls(
|
|
|
633
638
|
async def index_documents(
|
|
634
639
|
documents: List[Document],
|
|
635
640
|
agent_id: str,
|
|
636
|
-
|
|
641
|
+
vector_manager: VectorStoreManager,
|
|
637
642
|
chunk_size: int = DEFAULT_CHUNK_SIZE,
|
|
638
643
|
chunk_overlap: int = DEFAULT_CHUNK_OVERLAP,
|
|
639
644
|
) -> Tuple[int, bool]:
|
|
@@ -650,13 +655,12 @@ async def index_documents(
|
|
|
650
655
|
raise ValueError("No content could be processed into chunks")
|
|
651
656
|
|
|
652
657
|
# Handle vector store
|
|
653
|
-
|
|
654
|
-
vector_store, was_merged = await vs_manager.merge_with_existing(
|
|
658
|
+
vector_store, was_merged = await vector_manager.merge_with_existing(
|
|
655
659
|
split_docs, agent_id, chunk_size, chunk_overlap
|
|
656
660
|
)
|
|
657
661
|
|
|
658
662
|
# Save vector store
|
|
659
|
-
await
|
|
663
|
+
await vector_manager.save_vector_store(
|
|
660
664
|
vector_store, agent_id, chunk_size, chunk_overlap
|
|
661
665
|
)
|
|
662
666
|
|
|
@@ -250,10 +250,7 @@ Extract the URLs now:"""
|
|
|
250
250
|
"""Call OpenAI GPT-4o-mini to extract URLs from sitemap content."""
|
|
251
251
|
try:
|
|
252
252
|
# Get OpenAI API key using the standard pattern
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
temp_tool = OpenAIBaseTool(skill_store=self.skill_store)
|
|
256
|
-
api_key = temp_tool.get_api_key()
|
|
253
|
+
api_key = self.get_openai_api_key()
|
|
257
254
|
|
|
258
255
|
# Initialize OpenAI client
|
|
259
256
|
client = openai.AsyncOpenAI(api_key=api_key)
|
|
@@ -385,9 +382,12 @@ Extract the URLs now:"""
|
|
|
385
382
|
f"[{agent_id}] Extracted {len(unique_urls)} URLs from sitemaps. Scraping and indexing..."
|
|
386
383
|
)
|
|
387
384
|
|
|
385
|
+
embedding_api_key = self.get_openai_api_key()
|
|
386
|
+
vector_manager = VectorStoreManager(embedding_api_key)
|
|
387
|
+
|
|
388
388
|
# Use the utility function to scrape and index URLs directly
|
|
389
389
|
total_chunks, was_merged, valid_urls = await scrape_and_index_urls(
|
|
390
|
-
unique_urls, agent_id,
|
|
390
|
+
unique_urls, agent_id, vector_manager, chunk_size, chunk_overlap
|
|
391
391
|
)
|
|
392
392
|
|
|
393
393
|
if total_chunks == 0:
|
|
@@ -397,12 +397,11 @@ Extract the URLs now:"""
|
|
|
397
397
|
return f"Error: No content could be extracted from the discovered URLs. Found sitemaps: {', '.join(found_sitemaps)}"
|
|
398
398
|
|
|
399
399
|
# Get current storage size for response
|
|
400
|
-
|
|
401
|
-
current_size = await vs_manager.get_content_size(agent_id)
|
|
400
|
+
current_size = await vector_manager.get_content_size(agent_id)
|
|
402
401
|
size_limit_reached = len(valid_urls) < len(unique_urls)
|
|
403
402
|
|
|
404
403
|
# Update metadata
|
|
405
|
-
metadata_manager = MetadataManager(
|
|
404
|
+
metadata_manager = MetadataManager(vector_manager)
|
|
406
405
|
new_metadata = metadata_manager.create_url_metadata(
|
|
407
406
|
valid_urls, [], "website_indexer"
|
|
408
407
|
)
|
|
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Optional, TypedDict
|
|
|
4
4
|
|
|
5
5
|
from coinbase_agentkit import weth_action_provider
|
|
6
6
|
|
|
7
|
-
from intentkit.abstracts.skill import SkillStoreABC
|
|
8
7
|
from intentkit.skills.base import (
|
|
9
8
|
SkillConfig,
|
|
10
9
|
SkillState,
|
|
@@ -30,7 +29,6 @@ class Config(SkillConfig):
|
|
|
30
29
|
async def get_skills(
|
|
31
30
|
config: "Config",
|
|
32
31
|
is_private: bool,
|
|
33
|
-
store: SkillStoreABC,
|
|
34
32
|
agent_id: str,
|
|
35
33
|
agent: Optional["Agent"] = None,
|
|
36
34
|
**_,
|
|
@@ -44,9 +42,7 @@ async def get_skills(
|
|
|
44
42
|
if state == "public" or (state == "private" and is_private):
|
|
45
43
|
available_skills.append(skill_name)
|
|
46
44
|
|
|
47
|
-
actions = await get_agentkit_actions(
|
|
48
|
-
agent_id, store, [weth_action_provider], agent=agent
|
|
49
|
-
)
|
|
45
|
+
actions = await get_agentkit_actions(agent_id, [weth_action_provider], agent=agent)
|
|
50
46
|
tools: list[WethBaseTool] = []
|
|
51
47
|
for skill in available_skills:
|
|
52
48
|
for action in actions:
|
intentkit/skills/wow/__init__.py
CHANGED
|
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Optional, TypedDict
|
|
|
4
4
|
|
|
5
5
|
from coinbase_agentkit import wow_action_provider
|
|
6
6
|
|
|
7
|
-
from intentkit.abstracts.skill import SkillStoreABC
|
|
8
7
|
from intentkit.skills.base import (
|
|
9
8
|
SkillConfig,
|
|
10
9
|
SkillState,
|
|
@@ -32,7 +31,6 @@ class Config(SkillConfig):
|
|
|
32
31
|
async def get_skills(
|
|
33
32
|
config: "Config",
|
|
34
33
|
is_private: bool,
|
|
35
|
-
store: SkillStoreABC,
|
|
36
34
|
agent_id: str,
|
|
37
35
|
agent: Optional["Agent"] = None,
|
|
38
36
|
**_,
|
|
@@ -46,9 +44,7 @@ async def get_skills(
|
|
|
46
44
|
if state == "public" or (state == "private" and is_private):
|
|
47
45
|
available_skills.append(skill_name)
|
|
48
46
|
|
|
49
|
-
actions = await get_agentkit_actions(
|
|
50
|
-
agent_id, store, [wow_action_provider], agent=agent
|
|
51
|
-
)
|
|
47
|
+
actions = await get_agentkit_actions(agent_id, [wow_action_provider], agent=agent)
|
|
52
48
|
tools: list[WowBaseTool] = []
|
|
53
49
|
for skill in available_skills:
|
|
54
50
|
for action in actions:
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""x402 skill category."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
from intentkit.skills.base import SkillConfig, SkillState
|
|
7
|
+
from intentkit.skills.x402.ask_agent import X402AskAgent
|
|
8
|
+
from intentkit.skills.x402.base import X402BaseSkill
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
_cache: dict[str, X402BaseSkill] = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SkillStates(TypedDict):
|
|
16
|
+
x402_ask_agent: SkillState
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Config(SkillConfig):
|
|
20
|
+
"""Configuration for x402 skills."""
|
|
21
|
+
|
|
22
|
+
states: SkillStates
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def get_skills(
|
|
26
|
+
config: "Config",
|
|
27
|
+
is_private: bool,
|
|
28
|
+
**_,
|
|
29
|
+
) -> list[X402BaseSkill]:
|
|
30
|
+
"""Return enabled x402 skills for the agent."""
|
|
31
|
+
enabled_skills = []
|
|
32
|
+
for skill_name, state in config["states"].items():
|
|
33
|
+
if state == "disabled":
|
|
34
|
+
continue
|
|
35
|
+
if state == "public" or (state == "private" and is_private):
|
|
36
|
+
enabled_skills.append(skill_name)
|
|
37
|
+
|
|
38
|
+
result: list[X402BaseSkill] = []
|
|
39
|
+
for name in enabled_skills:
|
|
40
|
+
skill = _get_skill(name)
|
|
41
|
+
if skill:
|
|
42
|
+
result.append(skill)
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_skill(name: str) -> X402BaseSkill | None:
|
|
47
|
+
if name == "x402_ask_agent":
|
|
48
|
+
if name not in _cache:
|
|
49
|
+
_cache[name] = X402AskAgent()
|
|
50
|
+
return _cache[name]
|
|
51
|
+
|
|
52
|
+
logger.warning("Unknown x402 skill requested: %s", name)
|
|
53
|
+
return None
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Type
|
|
2
|
+
|
|
3
|
+
from langchain_core.tools import ToolException
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from x402.clients.httpx import x402HttpxClient
|
|
6
|
+
|
|
7
|
+
from intentkit.config.config import config
|
|
8
|
+
from intentkit.models.chat import AuthorType
|
|
9
|
+
from intentkit.skills.x402.base import X402BaseSkill
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AskAgentInput(BaseModel):
|
|
13
|
+
"""Arguments for the x402 ask agent skill."""
|
|
14
|
+
|
|
15
|
+
agent_id: str = Field(description="ID or slug of the agent to query.")
|
|
16
|
+
message: str = Field(description="Message to send to the target agent.")
|
|
17
|
+
search_mode: Optional[bool] = Field(
|
|
18
|
+
default=None, description="Enable search mode when interacting with the agent."
|
|
19
|
+
)
|
|
20
|
+
super_mode: Optional[bool] = Field(
|
|
21
|
+
default=None, description="Enable super mode when interacting with the agent."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class X402AskAgent(X402BaseSkill):
|
|
26
|
+
"""Skill that queries another agent via the x402 API."""
|
|
27
|
+
|
|
28
|
+
name: str = "x402_ask_agent"
|
|
29
|
+
description: str = (
|
|
30
|
+
"Call another agent through the x402 API and return the final agent message."
|
|
31
|
+
)
|
|
32
|
+
args_schema: Type[BaseModel] = AskAgentInput
|
|
33
|
+
|
|
34
|
+
async def _arun(
|
|
35
|
+
self,
|
|
36
|
+
agent_id: str,
|
|
37
|
+
message: str,
|
|
38
|
+
search_mode: Optional[bool] = None,
|
|
39
|
+
super_mode: Optional[bool] = None,
|
|
40
|
+
) -> str:
|
|
41
|
+
base_url = (config.open_api_base_url or "").rstrip("/")
|
|
42
|
+
if not base_url:
|
|
43
|
+
raise ValueError("X402 API base URL is not configured.")
|
|
44
|
+
|
|
45
|
+
# Ensure an EVM account exists and retrieve wallet provider signer
|
|
46
|
+
evm_account = await self.get_evm_account()
|
|
47
|
+
|
|
48
|
+
payload: Dict[str, Any] = {
|
|
49
|
+
"agent_id": agent_id,
|
|
50
|
+
"message": message,
|
|
51
|
+
"app_id": "skill",
|
|
52
|
+
}
|
|
53
|
+
if search_mode is not None:
|
|
54
|
+
payload["search_mode"] = search_mode
|
|
55
|
+
if super_mode is not None:
|
|
56
|
+
payload["super_mode"] = super_mode
|
|
57
|
+
|
|
58
|
+
async with x402HttpxClient(
|
|
59
|
+
account=evm_account,
|
|
60
|
+
base_url=base_url,
|
|
61
|
+
timeout=20.0,
|
|
62
|
+
) as client:
|
|
63
|
+
response = await client.post("/x402", json=payload)
|
|
64
|
+
response.raise_for_status()
|
|
65
|
+
messages = response.json()
|
|
66
|
+
if not isinstance(messages, list) or not messages:
|
|
67
|
+
raise ValueError("Agent returned an empty response.")
|
|
68
|
+
|
|
69
|
+
last_message = messages[-1]
|
|
70
|
+
if not isinstance(last_message, dict):
|
|
71
|
+
raise ValueError("Agent response format is invalid.")
|
|
72
|
+
|
|
73
|
+
author_type = last_message.get("author_type")
|
|
74
|
+
content = last_message.get("message")
|
|
75
|
+
|
|
76
|
+
if author_type == AuthorType.SYSTEM.value:
|
|
77
|
+
raise ToolException(content or "Agent returned a system message.")
|
|
78
|
+
|
|
79
|
+
if not content:
|
|
80
|
+
raise ToolException("Agent response did not include message text.")
|
|
81
|
+
|
|
82
|
+
return str(content)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"title": "x402",
|
|
5
|
+
"description": "Interact with other IntentKit agents through the x402 payment protocol.",
|
|
6
|
+
"x-icon": "https://ai.service.crestal.dev/skills/x402/x402.png",
|
|
7
|
+
"x-tags": [
|
|
8
|
+
"Communication",
|
|
9
|
+
"Infrastructure"
|
|
10
|
+
],
|
|
11
|
+
"properties": {
|
|
12
|
+
"enabled": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"title": "Enabled",
|
|
15
|
+
"description": "Whether this skill category is enabled.",
|
|
16
|
+
"default": false
|
|
17
|
+
},
|
|
18
|
+
"states": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"x402_ask_agent": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"title": "Ask Agent",
|
|
24
|
+
"enum": [
|
|
25
|
+
"disabled",
|
|
26
|
+
"public",
|
|
27
|
+
"private"
|
|
28
|
+
],
|
|
29
|
+
"x-enum-title": [
|
|
30
|
+
"Disabled",
|
|
31
|
+
"Agent Owner + All Users",
|
|
32
|
+
"Agent Owner Only"
|
|
33
|
+
],
|
|
34
|
+
"description": "Send a message to another IntentKit agent via the x402 API and return the final agent response.",
|
|
35
|
+
"default": "disabled"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
Binary file
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from typing import TypedDict
|
|
5
5
|
|
|
6
|
-
from intentkit.abstracts.skill import SkillStoreABC
|
|
7
6
|
from intentkit.skills.base import SkillConfig, SkillState
|
|
8
7
|
from intentkit.skills.xmtp.base import XmtpBaseTool
|
|
9
8
|
from intentkit.skills.xmtp.price import XmtpGetSwapPrice
|
|
@@ -31,7 +30,6 @@ class Config(SkillConfig):
|
|
|
31
30
|
async def get_skills(
|
|
32
31
|
config: "Config",
|
|
33
32
|
is_private: bool,
|
|
34
|
-
store: SkillStoreABC,
|
|
35
33
|
**_,
|
|
36
34
|
) -> list[XmtpBaseTool]:
|
|
37
35
|
"""Get all XMTP skills.
|
|
@@ -39,7 +37,6 @@ async def get_skills(
|
|
|
39
37
|
Args:
|
|
40
38
|
config: The configuration for XMTP skills.
|
|
41
39
|
is_private: Whether to include private skills.
|
|
42
|
-
store: The skill store for persisting data.
|
|
43
40
|
|
|
44
41
|
Returns:
|
|
45
42
|
A list of XMTP skills.
|
|
@@ -56,7 +53,7 @@ async def get_skills(
|
|
|
56
53
|
# Get each skill using the cached getter
|
|
57
54
|
result = []
|
|
58
55
|
for name in available_skills:
|
|
59
|
-
skill = get_xmtp_skill(name
|
|
56
|
+
skill = get_xmtp_skill(name)
|
|
60
57
|
if skill:
|
|
61
58
|
result.append(skill)
|
|
62
59
|
return result
|
|
@@ -64,34 +61,26 @@ async def get_skills(
|
|
|
64
61
|
|
|
65
62
|
def get_xmtp_skill(
|
|
66
63
|
name: str,
|
|
67
|
-
store: SkillStoreABC,
|
|
68
64
|
) -> XmtpBaseTool:
|
|
69
65
|
"""Get an XMTP skill by name.
|
|
70
66
|
|
|
71
67
|
Args:
|
|
72
68
|
name: The name of the skill to get
|
|
73
|
-
store: The skill store for persisting data
|
|
74
69
|
|
|
75
70
|
Returns:
|
|
76
71
|
The requested XMTP skill
|
|
77
72
|
"""
|
|
78
73
|
if name == "xmtp_transfer":
|
|
79
74
|
if name not in _cache:
|
|
80
|
-
_cache[name] = XmtpTransfer(
|
|
81
|
-
skill_store=store,
|
|
82
|
-
)
|
|
75
|
+
_cache[name] = XmtpTransfer()
|
|
83
76
|
return _cache[name]
|
|
84
77
|
elif name == "xmtp_swap":
|
|
85
78
|
if name not in _cache:
|
|
86
|
-
_cache[name] = XmtpSwap(
|
|
87
|
-
skill_store=store,
|
|
88
|
-
)
|
|
79
|
+
_cache[name] = XmtpSwap()
|
|
89
80
|
return _cache[name]
|
|
90
81
|
elif name == "xmtp_get_swap_price":
|
|
91
82
|
if name not in _cache:
|
|
92
|
-
_cache[name] = XmtpGetSwapPrice(
|
|
93
|
-
skill_store=store,
|
|
94
|
-
)
|
|
83
|
+
_cache[name] = XmtpGetSwapPrice()
|
|
95
84
|
return _cache[name]
|
|
96
85
|
else:
|
|
97
86
|
logger.warning(f"Unknown XMTP skill: {name}")
|
intentkit/skills/xmtp/base.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from typing import Dict, Literal
|
|
2
2
|
|
|
3
|
-
from intentkit.skills.
|
|
3
|
+
from intentkit.skills.onchain import IntentKitOnChainSkill
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class XmtpBaseTool(
|
|
6
|
+
class XmtpBaseTool(IntentKitOnChainSkill):
|
|
7
7
|
"""Base class for XMTP-related skills."""
|
|
8
8
|
|
|
9
9
|
# Set response format to content_and_artifact for returning tuple
|
intentkit/skills/xmtp/price.py
CHANGED
|
@@ -3,7 +3,7 @@ from typing import Literal, Type
|
|
|
3
3
|
from langchain_core.tools.base import ToolException
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
|
-
from intentkit.clients.cdp import
|
|
6
|
+
from intentkit.clients.cdp import get_cdp_client
|
|
7
7
|
from intentkit.skills.xmtp.base import XmtpBaseTool
|
|
8
8
|
|
|
9
9
|
|
|
@@ -50,8 +50,8 @@ class XmtpGetSwapPrice(XmtpBaseTool):
|
|
|
50
50
|
|
|
51
51
|
network_for_cdp = self.get_cdp_network(agent.network_id)
|
|
52
52
|
|
|
53
|
-
cdp_client =
|
|
54
|
-
# Note: Don't use async with context manager as
|
|
53
|
+
cdp_client = get_cdp_client()
|
|
54
|
+
# Note: Don't use async with context manager as get_cdp_client returns a managed global client
|
|
55
55
|
price = await cdp_client.evm.get_swap_price(
|
|
56
56
|
from_token=from_token,
|
|
57
57
|
to_token=to_token,
|
intentkit/skills/xmtp/swap.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import List, Tuple, Type
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
-
from intentkit.clients.cdp import
|
|
5
|
+
from intentkit.clients.cdp import get_cdp_client
|
|
6
6
|
from intentkit.models.chat import ChatMessageAttachment, ChatMessageAttachmentType
|
|
7
7
|
from intentkit.skills.xmtp.base import XmtpBaseTool
|
|
8
8
|
|
|
@@ -107,15 +107,15 @@ class XmtpSwap(XmtpBaseTool):
|
|
|
107
107
|
# https://github.com/coinbase/cdp-sdk/blob/main/examples/python/evm/swaps/create_swap_quote.py
|
|
108
108
|
network_for_cdp = self.get_cdp_network(agent.network_id)
|
|
109
109
|
|
|
110
|
-
# Get CDP client from global
|
|
111
|
-
cdp_client =
|
|
110
|
+
# Get CDP client from the global helper (server-side credentials)
|
|
111
|
+
cdp_client = get_cdp_client()
|
|
112
112
|
|
|
113
113
|
# Call CDP to create swap quote and extract call datas
|
|
114
114
|
# Be permissive with response shape across SDK versions
|
|
115
115
|
try:
|
|
116
116
|
# Attempt the canonical method per CDP SDK examples
|
|
117
117
|
# create_swap_quote(from_token, to_token, from_amount, network, taker, slippage_bps, signer_address)
|
|
118
|
-
# Note: Don't use async with context manager as
|
|
118
|
+
# Note: Don't use async with context manager as get_cdp_client returns a managed global client
|
|
119
119
|
quote = await cdp_client.evm.create_swap_quote(
|
|
120
120
|
from_token=from_token,
|
|
121
121
|
to_token=to_token,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""JSON Schema utilities for IntentKit.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for working with JSON schemas, including
|
|
4
|
+
resolving $defs references and generating nested schemas.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def resolve_schema_refs(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
12
|
+
"""Recursively resolve $defs references in a JSON schema.
|
|
13
|
+
|
|
14
|
+
This function takes a JSON schema with $defs references and returns
|
|
15
|
+
a fully nested schema without any $ref pointers. This is useful for
|
|
16
|
+
creating schemas that can be easily consumed by external systems
|
|
17
|
+
that don't support JSON Schema references.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
schema: The JSON schema dictionary that may contain $defs and $ref
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
dict: A new schema with all $ref resolved to nested objects
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> schema = {
|
|
27
|
+
... "type": "object",
|
|
28
|
+
... "properties": {
|
|
29
|
+
... "user": {"$ref": "#/$defs/User"}
|
|
30
|
+
... },
|
|
31
|
+
... "$defs": {
|
|
32
|
+
... "User": {
|
|
33
|
+
... "type": "object",
|
|
34
|
+
... "properties": {"name": {"type": "string"}}
|
|
35
|
+
... }
|
|
36
|
+
... }
|
|
37
|
+
... }
|
|
38
|
+
>>> resolved = resolve_schema_refs(schema)
|
|
39
|
+
>>> # resolved will have the User definition inlined
|
|
40
|
+
"""
|
|
41
|
+
# Deep copy to avoid modifying the original
|
|
42
|
+
resolved_schema = copy.deepcopy(schema)
|
|
43
|
+
|
|
44
|
+
# Extract $defs if they exist
|
|
45
|
+
defs = resolved_schema.pop("$defs", {})
|
|
46
|
+
|
|
47
|
+
def resolve_refs(obj: Any, defs_dict: Dict[str, Any]) -> Any:
|
|
48
|
+
"""Recursively resolve $ref in an object."""
|
|
49
|
+
if isinstance(obj, dict):
|
|
50
|
+
if "$ref" in obj:
|
|
51
|
+
ref_path = obj["$ref"]
|
|
52
|
+
if ref_path.startswith("#/$defs/"):
|
|
53
|
+
def_name = ref_path.replace("#/$defs/", "")
|
|
54
|
+
if def_name in defs_dict:
|
|
55
|
+
# Recursively resolve the referenced definition
|
|
56
|
+
resolved_def = resolve_refs(defs_dict[def_name], defs_dict)
|
|
57
|
+
return resolved_def
|
|
58
|
+
else:
|
|
59
|
+
# Keep the reference if definition not found
|
|
60
|
+
return obj
|
|
61
|
+
else:
|
|
62
|
+
# Keep non-$defs references as is
|
|
63
|
+
return obj
|
|
64
|
+
else:
|
|
65
|
+
# Recursively process all values in the dictionary
|
|
66
|
+
return {
|
|
67
|
+
key: resolve_refs(value, defs_dict) for key, value in obj.items()
|
|
68
|
+
}
|
|
69
|
+
elif isinstance(obj, list):
|
|
70
|
+
# Recursively process all items in the list
|
|
71
|
+
return [resolve_refs(item, defs_dict) for item in obj]
|
|
72
|
+
else:
|
|
73
|
+
# Return primitive values as is
|
|
74
|
+
return obj
|
|
75
|
+
|
|
76
|
+
# Resolve all references in the schema
|
|
77
|
+
return resolve_refs(resolved_schema, defs)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def create_array_schema(
|
|
81
|
+
item_schema: Dict[str, Any], resolve_refs: bool = True
|
|
82
|
+
) -> Dict[str, Any]:
|
|
83
|
+
"""Create an array schema with the given item schema.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
item_schema: The schema for array items
|
|
87
|
+
resolve_refs: Whether to resolve $defs references in the item schema
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
dict: Array schema with resolved item schema
|
|
91
|
+
"""
|
|
92
|
+
if resolve_refs:
|
|
93
|
+
resolved_item_schema = resolve_schema_refs(item_schema)
|
|
94
|
+
else:
|
|
95
|
+
resolved_item_schema = item_schema
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"type": "array",
|
|
99
|
+
"items": resolved_item_schema,
|
|
100
|
+
}
|