intentkit 0.8.12rc0__py3-none-any.whl → 0.8.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of intentkit might be problematic. Click here for more details.

Files changed (188) hide show
  1. intentkit/__init__.py +1 -1
  2. intentkit/abstracts/skill.py +2 -59
  3. intentkit/clients/__init__.py +3 -2
  4. intentkit/clients/cdp.py +63 -44
  5. intentkit/clients/twitter.py +35 -28
  6. intentkit/config/config.py +1 -0
  7. intentkit/core/agent.py +2 -279
  8. intentkit/core/asset.py +63 -16
  9. intentkit/core/engine.py +9 -5
  10. intentkit/core/scheduler.py +8 -8
  11. intentkit/models/agent.py +138 -94
  12. intentkit/models/agent_schema.json +6 -9
  13. intentkit/models/chat.py +1 -0
  14. intentkit/models/llm.csv +15 -12
  15. intentkit/models/skills.csv +5 -4
  16. intentkit/skills/acolyt/__init__.py +2 -9
  17. intentkit/skills/acolyt/base.py +2 -5
  18. intentkit/skills/aixbt/__init__.py +2 -13
  19. intentkit/skills/aixbt/base.py +0 -4
  20. intentkit/skills/aixbt/projects.py +1 -2
  21. intentkit/skills/allora/__init__.py +2 -9
  22. intentkit/skills/allora/base.py +2 -5
  23. intentkit/skills/base.py +101 -37
  24. intentkit/skills/basename/__init__.py +1 -3
  25. intentkit/skills/carv/__init__.py +116 -121
  26. intentkit/skills/carv/base.py +184 -185
  27. intentkit/skills/casino/__init__.py +4 -15
  28. intentkit/skills/casino/base.py +0 -4
  29. intentkit/skills/casino/deck_draw.py +1 -2
  30. intentkit/skills/casino/deck_shuffle.py +1 -2
  31. intentkit/skills/casino/dice_roll.py +1 -2
  32. intentkit/skills/cdp/__init__.py +0 -5
  33. intentkit/skills/cdp/base.py +0 -4
  34. intentkit/skills/cdp/schema.json +1 -17
  35. intentkit/skills/chainlist/__init__.py +2 -7
  36. intentkit/skills/chainlist/base.py +0 -4
  37. intentkit/skills/common/__init__.py +2 -9
  38. intentkit/skills/common/base.py +0 -4
  39. intentkit/skills/cookiefun/__init__.py +6 -9
  40. intentkit/skills/cookiefun/base.py +0 -4
  41. intentkit/skills/cryptocompare/__init__.py +7 -24
  42. intentkit/skills/cryptocompare/base.py +0 -5
  43. intentkit/skills/cryptopanic/__init__.py +3 -6
  44. intentkit/skills/cryptopanic/base.py +53 -55
  45. intentkit/skills/cryptopanic/fetch_crypto_news.py +0 -2
  46. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +1 -3
  47. intentkit/skills/dapplooker/__init__.py +2 -9
  48. intentkit/skills/dapplooker/base.py +2 -5
  49. intentkit/skills/defillama/__init__.py +24 -74
  50. intentkit/skills/defillama/base.py +0 -4
  51. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +2 -2
  52. intentkit/skills/defillama/coins/fetch_block.py +2 -2
  53. intentkit/skills/defillama/coins/fetch_current_prices.py +2 -2
  54. intentkit/skills/defillama/coins/fetch_first_price.py +2 -2
  55. intentkit/skills/defillama/coins/fetch_historical_prices.py +2 -2
  56. intentkit/skills/defillama/coins/fetch_price_chart.py +2 -2
  57. intentkit/skills/defillama/coins/fetch_price_percentage.py +2 -2
  58. intentkit/skills/defillama/fees/fetch_fees_overview.py +2 -2
  59. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +2 -2
  60. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +2 -2
  61. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +2 -2
  62. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +2 -2
  63. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -2
  64. intentkit/skills/defillama/tvl/fetch_chains.py +2 -2
  65. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -2
  66. intentkit/skills/defillama/tvl/fetch_protocol.py +2 -2
  67. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +2 -2
  68. intentkit/skills/defillama/tvl/fetch_protocols.py +2 -2
  69. intentkit/skills/defillama/volumes/fetch_dex_overview.py +2 -2
  70. intentkit/skills/defillama/volumes/fetch_dex_summary.py +2 -2
  71. intentkit/skills/defillama/volumes/fetch_options_overview.py +2 -2
  72. intentkit/skills/defillama/yields/fetch_pool_chart.py +2 -2
  73. intentkit/skills/defillama/yields/fetch_pools.py +2 -2
  74. intentkit/skills/dexscreener/__init__.py +97 -102
  75. intentkit/skills/dexscreener/base.py +125 -130
  76. intentkit/skills/dexscreener/get_pair_info.py +2 -3
  77. intentkit/skills/dexscreener/get_token_pairs.py +2 -3
  78. intentkit/skills/dexscreener/get_tokens_info.py +2 -3
  79. intentkit/skills/dexscreener/search_token.py +2 -4
  80. intentkit/skills/dune_analytics/__init__.py +4 -6
  81. intentkit/skills/dune_analytics/base.py +50 -52
  82. intentkit/skills/dune_analytics/fetch_kol_buys.py +0 -2
  83. intentkit/skills/dune_analytics/fetch_nation_metrics.py +0 -2
  84. intentkit/skills/elfa/__init__.py +5 -18
  85. intentkit/skills/elfa/base.py +8 -10
  86. intentkit/skills/enso/__init__.py +9 -29
  87. intentkit/skills/enso/base.py +3 -6
  88. intentkit/skills/enso/route.py +1 -3
  89. intentkit/skills/erc20/__init__.py +1 -5
  90. intentkit/skills/erc721/__init__.py +1 -3
  91. intentkit/skills/firecrawl/__init__.py +5 -18
  92. intentkit/skills/firecrawl/base.py +2 -5
  93. intentkit/skills/firecrawl/crawl.py +10 -9
  94. intentkit/skills/firecrawl/query.py +3 -1
  95. intentkit/skills/firecrawl/scrape.py +8 -10
  96. intentkit/skills/firecrawl/utils.py +25 -26
  97. intentkit/skills/github/__init__.py +2 -7
  98. intentkit/skills/github/base.py +0 -4
  99. intentkit/skills/heurist/__init__.py +8 -27
  100. intentkit/skills/heurist/base.py +2 -5
  101. intentkit/skills/heurist/image_generation_animagine_xl.py +5 -5
  102. intentkit/skills/heurist/image_generation_arthemy_comics.py +5 -5
  103. intentkit/skills/heurist/image_generation_arthemy_real.py +5 -5
  104. intentkit/skills/heurist/image_generation_braindance.py +5 -5
  105. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +5 -5
  106. intentkit/skills/heurist/image_generation_flux_1_dev.py +5 -5
  107. intentkit/skills/heurist/image_generation_sdxl.py +5 -5
  108. intentkit/skills/http/__init__.py +4 -15
  109. intentkit/skills/http/base.py +0 -4
  110. intentkit/skills/lifi/__init__.py +1 -6
  111. intentkit/skills/lifi/base.py +0 -4
  112. intentkit/skills/lifi/token_execute.py +1 -4
  113. intentkit/skills/lifi/token_quote.py +1 -3
  114. intentkit/skills/moralis/__init__.py +3 -7
  115. intentkit/skills/moralis/base.py +2 -5
  116. intentkit/skills/morpho/__init__.py +1 -3
  117. intentkit/skills/nation/__init__.py +2 -7
  118. intentkit/skills/nation/base.py +4 -7
  119. intentkit/skills/onchain.py +27 -0
  120. intentkit/skills/openai/__init__.py +5 -18
  121. intentkit/skills/openai/base.py +8 -10
  122. intentkit/skills/openai/dalle_image_generation.py +2 -5
  123. intentkit/skills/openai/gpt_image_generation.py +2 -5
  124. intentkit/skills/openai/gpt_image_to_image.py +2 -5
  125. intentkit/skills/openai/image_to_text.py +2 -5
  126. intentkit/skills/portfolio/__init__.py +11 -35
  127. intentkit/skills/portfolio/base.py +2 -5
  128. intentkit/skills/pyth/__init__.py +1 -5
  129. intentkit/skills/skills.toml +4 -0
  130. intentkit/skills/slack/__init__.py +5 -17
  131. intentkit/skills/slack/base.py +0 -4
  132. intentkit/skills/supabase/__init__.py +7 -23
  133. intentkit/skills/supabase/base.py +0 -4
  134. intentkit/skills/superfluid/__init__.py +1 -3
  135. intentkit/skills/system/__init__.py +7 -24
  136. intentkit/skills/system/add_autonomous_task.py +2 -2
  137. intentkit/skills/system/delete_autonomous_task.py +2 -2
  138. intentkit/skills/system/edit_autonomous_task.py +2 -4
  139. intentkit/skills/system/list_autonomous_tasks.py +2 -2
  140. intentkit/skills/system/read_agent_api_key.py +6 -4
  141. intentkit/skills/system/regenerate_agent_api_key.py +6 -4
  142. intentkit/skills/tavily/__init__.py +3 -12
  143. intentkit/skills/tavily/base.py +2 -5
  144. intentkit/skills/tavily/tavily_extract.py +1 -2
  145. intentkit/skills/tavily/tavily_search.py +3 -3
  146. intentkit/skills/token/__init__.py +5 -10
  147. intentkit/skills/token/base.py +2 -6
  148. intentkit/skills/twitter/__init__.py +11 -35
  149. intentkit/skills/twitter/base.py +14 -16
  150. intentkit/skills/twitter/follow_user.py +0 -1
  151. intentkit/skills/twitter/get_mentions.py +0 -1
  152. intentkit/skills/twitter/get_timeline.py +0 -1
  153. intentkit/skills/twitter/get_user_by_username.py +0 -1
  154. intentkit/skills/twitter/get_user_tweets.py +0 -1
  155. intentkit/skills/twitter/like_tweet.py +0 -1
  156. intentkit/skills/twitter/post_tweet.py +2 -2
  157. intentkit/skills/twitter/reply_tweet.py +2 -2
  158. intentkit/skills/twitter/retweet.py +0 -1
  159. intentkit/skills/twitter/search_tweets.py +0 -1
  160. intentkit/skills/unrealspeech/__init__.py +2 -7
  161. intentkit/skills/unrealspeech/base.py +0 -4
  162. intentkit/skills/venice_audio/__init__.py +99 -106
  163. intentkit/skills/venice_audio/base.py +118 -121
  164. intentkit/skills/venice_audio/venice_audio.py +1 -5
  165. intentkit/skills/venice_image/__init__.py +147 -154
  166. intentkit/skills/venice_image/base.py +185 -192
  167. intentkit/skills/web_scraper/__init__.py +5 -18
  168. intentkit/skills/web_scraper/base.py +20 -4
  169. intentkit/skills/web_scraper/document_indexer.py +6 -4
  170. intentkit/skills/web_scraper/scrape_and_index.py +11 -8
  171. intentkit/skills/web_scraper/utils.py +31 -27
  172. intentkit/skills/web_scraper/website_indexer.py +7 -8
  173. intentkit/skills/weth/__init__.py +1 -5
  174. intentkit/skills/wow/__init__.py +1 -5
  175. intentkit/skills/x402/__init__.py +53 -0
  176. intentkit/skills/x402/ask_agent.py +141 -0
  177. intentkit/skills/x402/base.py +9 -0
  178. intentkit/skills/x402/schema.json +40 -0
  179. intentkit/skills/x402/x402.png +0 -0
  180. intentkit/skills/xmtp/__init__.py +4 -15
  181. intentkit/skills/xmtp/base.py +2 -2
  182. intentkit/skills/xmtp/price.py +3 -3
  183. intentkit/skills/xmtp/swap.py +4 -4
  184. intentkit/utils/schema.py +100 -0
  185. {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dist-info}/METADATA +1 -1
  186. {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dist-info}/RECORD +188 -181
  187. {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.dist-info}/WHEEL +0 -0
  188. {intentkit-0.8.12rc0.dist-info → intentkit-0.8.13.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.abstracts.skill import SkillStoreABC
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, skill_store: SkillStoreABC):
67
- self.skill_store = skill_store
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 system API key."""
71
- api_key = self.skill_store.get_system_config("openai_api_key")
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
- def format_size(self, size_bytes: int) -> str:
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, skill_store: SkillStoreABC):
316
- self.skill_store = skill_store
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
- vs_manager = VectorStoreManager(self.skill_store)
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
- vs_manager = VectorStoreManager(self.skill_store)
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
- vs_manager = VectorStoreManager(None) # Just for formatting
457
- formatted_size = vs_manager.format_size(current_size_bytes)
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
- skill_store: SkillStoreABC,
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
- skill_store: Skill store instance
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
- vs_manager = VectorStoreManager(skill_store)
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: {vs_manager.format_size(current_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: {vs_manager.format_size(current_size)}"
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, skill_store, chunk_size, chunk_overlap
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: {vs_manager.format_size(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
- skill_store: SkillStoreABC,
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
- vs_manager = VectorStoreManager(skill_store)
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 vs_manager.save_vector_store(
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
- from intentkit.skills.openai.base import OpenAIBaseTool
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, self.skill_store, chunk_size, chunk_overlap
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
- vs_manager = VectorStoreManager(self.skill_store)
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(self.skill_store)
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:
@@ -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,141 @@
1
+ import threading
2
+ from typing import Any, Dict, Optional, Type
3
+
4
+ from coinbase_agentkit.wallet_providers.evm_wallet_provider import (
5
+ EvmWalletSigner as CoinbaseEvmWalletSigner,
6
+ )
7
+ from langchain_core.tools import ToolException
8
+ from pydantic import BaseModel, Field
9
+ from x402.clients.httpx import x402HttpxClient
10
+
11
+ from intentkit.clients import get_wallet_provider
12
+ from intentkit.config.config import config
13
+ from intentkit.models.chat import AuthorType
14
+ from intentkit.skills.x402.base import X402BaseSkill
15
+
16
+
17
+ class AskAgentInput(BaseModel):
18
+ """Arguments for the x402 ask agent skill."""
19
+
20
+ agent_id: str = Field(description="ID or slug of the agent to query.")
21
+ message: str = Field(description="Message to send to the target agent.")
22
+ search_mode: Optional[bool] = Field(
23
+ default=None, description="Enable search mode when interacting with the agent."
24
+ )
25
+ super_mode: Optional[bool] = Field(
26
+ default=None, description="Enable super mode when interacting with the agent."
27
+ )
28
+
29
+
30
+ class X402AskAgent(X402BaseSkill):
31
+ """Skill that queries another agent via the x402 API."""
32
+
33
+ name: str = "x402_ask_agent"
34
+ description: str = (
35
+ "Call another agent through the x402 API and return the final agent message."
36
+ )
37
+ args_schema: Type[BaseModel] = AskAgentInput
38
+
39
+ async def _arun(
40
+ self,
41
+ agent_id: str,
42
+ message: str,
43
+ search_mode: Optional[bool] = None,
44
+ super_mode: Optional[bool] = None,
45
+ ) -> str:
46
+ base_url = (config.open_api_base_url or "").rstrip("/")
47
+ if not base_url:
48
+ raise ValueError("X402 API base URL is not configured.")
49
+
50
+ # Use wallet provider signer to satisfy eth_account.BaseAccount interface requirements
51
+ context = self.get_context()
52
+ wallet_provider = await get_wallet_provider(context.agent)
53
+ account = ThreadSafeEvmWalletSigner(wallet_provider)
54
+
55
+ payload: Dict[str, Any] = {
56
+ "agent_id": agent_id,
57
+ "message": message,
58
+ "app_id": "skill",
59
+ }
60
+ if search_mode is not None:
61
+ payload["search_mode"] = search_mode
62
+ if super_mode is not None:
63
+ payload["super_mode"] = super_mode
64
+
65
+ async with x402HttpxClient(
66
+ account=account,
67
+ base_url=base_url,
68
+ timeout=20.0,
69
+ ) as client:
70
+ response = await client.post("/x402", json=payload)
71
+ response.raise_for_status()
72
+ messages = response.json()
73
+ if not isinstance(messages, list) or not messages:
74
+ raise ValueError("Agent returned an empty response.")
75
+
76
+ last_message = messages[-1]
77
+ if not isinstance(last_message, dict):
78
+ raise ValueError("Agent response format is invalid.")
79
+
80
+ author_type = last_message.get("author_type")
81
+ content = last_message.get("message")
82
+
83
+ if author_type == AuthorType.SYSTEM.value:
84
+ raise ToolException(content or "Agent returned a system message.")
85
+
86
+ if not content:
87
+ raise ToolException("Agent response did not include message text.")
88
+
89
+ return str(content)
90
+
91
+
92
+ class ThreadSafeEvmWalletSigner(CoinbaseEvmWalletSigner):
93
+ """EVM wallet signer that avoids nested event loop errors.
94
+
95
+ Coinbase's signer runs async wallet calls in the current thread. When invoked
96
+ inside an active asyncio loop (as happens in async skills), it trips over the
97
+ loop already running. We hop work to a background thread so the provider can
98
+ spin up its own loop safely.
99
+ """
100
+
101
+ def _run_in_thread(self, func: Any, *args: Any, **kwargs: Any) -> Any:
102
+ result: list[Any] = []
103
+ error: list[BaseException] = []
104
+
105
+ def _target() -> None:
106
+ try:
107
+ result.append(func(*args, **kwargs))
108
+ except BaseException as exc: # pragma: no cover - bubble up original error
109
+ error.append(exc)
110
+
111
+ thread = threading.Thread(target=_target, daemon=True)
112
+ thread.start()
113
+ thread.join()
114
+
115
+ if error:
116
+ raise error[0]
117
+ return result[0] if result else None
118
+
119
+ def unsafe_sign_hash(self, message_hash: Any) -> Any:
120
+ return self._run_in_thread(super().unsafe_sign_hash, message_hash)
121
+
122
+ def sign_message(self, signable_message: Any) -> Any:
123
+ return self._run_in_thread(super().sign_message, signable_message)
124
+
125
+ def sign_transaction(self, transaction_dict: Any) -> Any:
126
+ return self._run_in_thread(super().sign_transaction, transaction_dict)
127
+
128
+ def sign_typed_data(
129
+ self,
130
+ domain_data: Optional[Dict[str, Any]] = None,
131
+ message_types: Optional[Dict[str, Any]] = None,
132
+ message_data: Optional[Dict[str, Any]] = None,
133
+ full_message: Optional[Dict[str, Any]] = None,
134
+ ) -> Any:
135
+ return self._run_in_thread(
136
+ super().sign_typed_data,
137
+ domain_data=domain_data,
138
+ message_types=message_types,
139
+ message_data=message_data,
140
+ full_message=full_message,
141
+ )
@@ -0,0 +1,9 @@
1
+ from intentkit.skills.onchain import IntentKitOnChainSkill
2
+
3
+
4
+ class X402BaseSkill(IntentKitOnChainSkill):
5
+ """Base class for x402 skills."""
6
+
7
+ @property
8
+ def category(self) -> str:
9
+ return "x402"
@@ -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, store)
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}")
@@ -1,9 +1,9 @@
1
1
  from typing import Dict, Literal
2
2
 
3
- from intentkit.skills.base import IntentKitSkill
3
+ from intentkit.skills.onchain import IntentKitOnChainSkill
4
4
 
5
5
 
6
- class XmtpBaseTool(IntentKitSkill):
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
@@ -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 get_origin_cdp_client
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 = get_origin_cdp_client()
54
- # Note: Don't use async with context manager as get_origin_cdp_client returns a managed global client
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,
@@ -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 get_origin_cdp_client
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 origin helper (server-side credentials)
111
- cdp_client = get_origin_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 get_origin_cdp_client returns a managed global client
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,