intentkit 0.6.9.dev1__py3-none-any.whl → 0.6.10__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 (170) hide show
  1. intentkit/__init__.py +1 -1
  2. intentkit/abstracts/graph.py +17 -2
  3. intentkit/core/engine.py +49 -30
  4. intentkit/core/node.py +10 -20
  5. intentkit/models/agent.py +215 -11
  6. intentkit/models/agent_schema.json +4 -0
  7. intentkit/models/chat.py +9 -1
  8. intentkit/models/llm.py +53 -0
  9. intentkit/skills/acolyt/ask.py +2 -5
  10. intentkit/skills/acolyt/base.py +16 -6
  11. intentkit/skills/aixbt/__init__.py +3 -7
  12. intentkit/skills/aixbt/projects.py +12 -36
  13. intentkit/skills/allora/base.py +16 -6
  14. intentkit/skills/allora/price.py +2 -4
  15. intentkit/skills/base.py +8 -1
  16. intentkit/skills/carv/base.py +12 -10
  17. intentkit/skills/carv/fetch_news.py +90 -92
  18. intentkit/skills/carv/onchain_query.py +162 -164
  19. intentkit/skills/carv/token_info_and_price.py +108 -110
  20. intentkit/skills/chainlist/chain_lookup.py +1 -2
  21. intentkit/skills/common/current_time.py +1 -2
  22. intentkit/skills/cookiefun/base.py +20 -12
  23. intentkit/skills/cookiefun/get_account_details.py +1 -3
  24. intentkit/skills/cookiefun/get_account_feed.py +1 -3
  25. intentkit/skills/cookiefun/get_account_smart_followers.py +1 -3
  26. intentkit/skills/cookiefun/get_sectors.py +2 -3
  27. intentkit/skills/cookiefun/search_accounts.py +1 -3
  28. intentkit/skills/cryptocompare/fetch_news.py +3 -4
  29. intentkit/skills/cryptocompare/fetch_price.py +3 -4
  30. intentkit/skills/cryptocompare/fetch_top_exchanges.py +3 -4
  31. intentkit/skills/cryptocompare/fetch_top_market_cap.py +3 -4
  32. intentkit/skills/cryptocompare/fetch_top_volume.py +3 -4
  33. intentkit/skills/cryptocompare/fetch_trading_signals.py +3 -4
  34. intentkit/skills/cryptopanic/base.py +13 -9
  35. intentkit/skills/cryptopanic/fetch_crypto_news.py +150 -153
  36. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +133 -136
  37. intentkit/skills/dapplooker/base.py +16 -6
  38. intentkit/skills/dapplooker/dapplooker_token_data.py +2 -4
  39. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +2 -3
  40. intentkit/skills/defillama/coins/fetch_block.py +2 -3
  41. intentkit/skills/defillama/coins/fetch_current_prices.py +2 -5
  42. intentkit/skills/defillama/coins/fetch_first_price.py +2 -5
  43. intentkit/skills/defillama/coins/fetch_historical_prices.py +2 -3
  44. intentkit/skills/defillama/coins/fetch_price_chart.py +2 -5
  45. intentkit/skills/defillama/coins/fetch_price_percentage.py +2 -5
  46. intentkit/skills/defillama/fees/fetch_fees_overview.py +2 -3
  47. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +2 -3
  48. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +2 -3
  49. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +2 -3
  50. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +2 -3
  51. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -5
  52. intentkit/skills/defillama/tvl/fetch_chains.py +2 -3
  53. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -3
  54. intentkit/skills/defillama/tvl/fetch_protocol.py +2 -5
  55. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +2 -5
  56. intentkit/skills/defillama/tvl/fetch_protocols.py +2 -3
  57. intentkit/skills/defillama/volumes/fetch_dex_overview.py +2 -3
  58. intentkit/skills/defillama/volumes/fetch_dex_summary.py +2 -5
  59. intentkit/skills/defillama/volumes/fetch_options_overview.py +2 -3
  60. intentkit/skills/defillama/yields/fetch_pool_chart.py +2 -5
  61. intentkit/skills/defillama/yields/fetch_pools.py +2 -3
  62. intentkit/skills/dune_analytics/base.py +15 -9
  63. intentkit/skills/dune_analytics/fetch_kol_buys.py +125 -128
  64. intentkit/skills/dune_analytics/fetch_nation_metrics.py +234 -237
  65. intentkit/skills/elfa/base.py +16 -6
  66. intentkit/skills/elfa/mention.py +2 -7
  67. intentkit/skills/elfa/stats.py +2 -6
  68. intentkit/skills/elfa/tokens.py +1 -4
  69. intentkit/skills/enso/base.py +25 -13
  70. intentkit/skills/enso/best_yield.py +1 -4
  71. intentkit/skills/enso/networks.py +2 -5
  72. intentkit/skills/enso/prices.py +1 -5
  73. intentkit/skills/enso/route.py +2 -5
  74. intentkit/skills/enso/tokens.py +1 -4
  75. intentkit/skills/enso/wallet.py +3 -9
  76. intentkit/skills/firecrawl/base.py +16 -6
  77. intentkit/skills/firecrawl/clear.py +1 -3
  78. intentkit/skills/firecrawl/crawl.py +7 -8
  79. intentkit/skills/firecrawl/query.py +7 -9
  80. intentkit/skills/firecrawl/scrape.py +7 -8
  81. intentkit/skills/github/github_search.py +1 -3
  82. intentkit/skills/heurist/base.py +15 -0
  83. intentkit/skills/heurist/image_generation_animagine_xl.py +3 -4
  84. intentkit/skills/heurist/image_generation_arthemy_comics.py +3 -4
  85. intentkit/skills/heurist/image_generation_arthemy_real.py +3 -4
  86. intentkit/skills/heurist/image_generation_braindance.py +3 -4
  87. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +3 -4
  88. intentkit/skills/heurist/image_generation_flux_1_dev.py +3 -4
  89. intentkit/skills/heurist/image_generation_sdxl.py +3 -4
  90. intentkit/skills/http/get.py +0 -2
  91. intentkit/skills/http/post.py +0 -2
  92. intentkit/skills/http/put.py +0 -2
  93. intentkit/skills/lifi/token_execute.py +1 -3
  94. intentkit/skills/lifi/token_quote.py +0 -2
  95. intentkit/skills/moralis/base.py +15 -1
  96. intentkit/skills/nation/nft_check.py +2 -5
  97. intentkit/skills/openai/base.py +14 -5
  98. intentkit/skills/openai/dalle_image_generation.py +6 -5
  99. intentkit/skills/openai/gpt_image_generation.py +6 -5
  100. intentkit/skills/openai/gpt_image_to_image.py +6 -5
  101. intentkit/skills/openai/image_to_text.py +6 -6
  102. intentkit/skills/portfolio/base.py +4 -3
  103. intentkit/skills/portfolio/token_balances.py +2 -4
  104. intentkit/skills/portfolio/wallet_approvals.py +2 -4
  105. intentkit/skills/portfolio/wallet_defi_positions.py +3 -4
  106. intentkit/skills/portfolio/wallet_history.py +2 -4
  107. intentkit/skills/portfolio/wallet_net_worth.py +2 -4
  108. intentkit/skills/portfolio/wallet_nfts.py +2 -4
  109. intentkit/skills/portfolio/wallet_profitability.py +2 -4
  110. intentkit/skills/portfolio/wallet_profitability_summary.py +2 -4
  111. intentkit/skills/portfolio/wallet_stats.py +2 -4
  112. intentkit/skills/portfolio/wallet_swaps.py +2 -4
  113. intentkit/skills/slack/base.py +18 -0
  114. intentkit/skills/slack/get_channel.py +3 -4
  115. intentkit/skills/slack/get_message.py +3 -4
  116. intentkit/skills/slack/schedule_message.py +3 -4
  117. intentkit/skills/slack/send_message.py +3 -4
  118. intentkit/skills/supabase/delete_data.py +3 -6
  119. intentkit/skills/supabase/fetch_data.py +3 -6
  120. intentkit/skills/supabase/insert_data.py +3 -6
  121. intentkit/skills/supabase/invoke_function.py +3 -6
  122. intentkit/skills/supabase/update_data.py +3 -6
  123. intentkit/skills/supabase/upsert_data.py +3 -6
  124. intentkit/skills/system/add_autonomous_task.py +1 -3
  125. intentkit/skills/system/delete_autonomous_task.py +1 -3
  126. intentkit/skills/system/edit_autonomous_task.py +1 -3
  127. intentkit/skills/system/list_autonomous_tasks.py +1 -3
  128. intentkit/skills/system/read_agent_api_key.py +2 -3
  129. intentkit/skills/system/regenerate_agent_api_key.py +2 -5
  130. intentkit/skills/tavily/base.py +14 -5
  131. intentkit/skills/tavily/tavily_extract.py +7 -8
  132. intentkit/skills/tavily/tavily_search.py +11 -9
  133. intentkit/skills/token/base.py +4 -6
  134. intentkit/skills/token/erc20_transfers.py +2 -4
  135. intentkit/skills/token/token_analytics.py +2 -4
  136. intentkit/skills/token/token_price.py +2 -4
  137. intentkit/skills/token/token_search.py +2 -4
  138. intentkit/skills/twitter/base.py +41 -0
  139. intentkit/skills/twitter/follow_user.py +4 -4
  140. intentkit/skills/twitter/get_mentions.py +4 -4
  141. intentkit/skills/twitter/get_timeline.py +4 -4
  142. intentkit/skills/twitter/get_user_by_username.py +4 -4
  143. intentkit/skills/twitter/get_user_tweets.py +4 -4
  144. intentkit/skills/twitter/like_tweet.py +4 -4
  145. intentkit/skills/twitter/post_tweet.py +3 -4
  146. intentkit/skills/twitter/reply_tweet.py +3 -4
  147. intentkit/skills/twitter/retweet.py +4 -4
  148. intentkit/skills/twitter/search_tweets.py +4 -4
  149. intentkit/skills/unrealspeech/base.py +16 -0
  150. intentkit/skills/unrealspeech/text_to_speech.py +4 -4
  151. intentkit/skills/venice_audio/base.py +11 -9
  152. intentkit/skills/venice_audio/venice_audio.py +238 -240
  153. intentkit/skills/venice_image/base.py +23 -19
  154. intentkit/skills/venice_image/image_enhance/image_enhance.py +78 -80
  155. intentkit/skills/venice_image/image_generation/image_generation_base.py +115 -117
  156. intentkit/skills/venice_image/image_upscale/image_upscale.py +88 -90
  157. intentkit/skills/venice_image/image_vision/image_vision.py +98 -100
  158. intentkit/skills/web_scraper/document_indexer.py +3 -5
  159. intentkit/skills/web_scraper/scrape_and_index.py +14 -17
  160. intentkit/skills/web_scraper/website_indexer.py +8 -10
  161. intentkit/skills/xmtp/README.md +110 -0
  162. intentkit/skills/xmtp/__init__.py +82 -0
  163. intentkit/skills/xmtp/base.py +15 -0
  164. intentkit/skills/xmtp/schema.json +43 -0
  165. intentkit/skills/xmtp/transfer.py +155 -0
  166. intentkit/skills/xmtp/xmtp.png +0 -0
  167. {intentkit-0.6.9.dev1.dist-info → intentkit-0.6.10.dist-info}/METADATA +4 -3
  168. {intentkit-0.6.9.dev1.dist-info → intentkit-0.6.10.dist-info}/RECORD +170 -164
  169. {intentkit-0.6.9.dev1.dist-info → intentkit-0.6.10.dist-info}/WHEEL +0 -0
  170. {intentkit-0.6.9.dev1.dist-info → intentkit-0.6.10.dist-info}/licenses/LICENSE +0 -0
@@ -2,7 +2,6 @@ import logging
2
2
  from typing import Dict, Literal, Type
3
3
 
4
4
  import httpx
5
- from langchain_core.runnables import RunnableConfig
6
5
  from pydantic import BaseModel, Field
7
6
 
8
7
  from intentkit.skills.acolyt.base import AcolytBaseTool
@@ -67,12 +66,11 @@ class AcolytAskGpt(AcolytBaseTool):
67
66
  """
68
67
  args_schema: Type[BaseModel] = AcolytAskGptInput
69
68
 
70
- async def _arun(self, question: str, config: RunnableConfig, **kwargs) -> Dict:
69
+ async def _arun(self, question: str, **kwargs) -> Dict:
71
70
  """Run the tool to get answer from Acolyt GPT.
72
71
 
73
72
  Args:
74
73
  question (str): The question body from user.
75
- config (RunnableConfig): The configuration for the runnable, containing agent context.
76
74
 
77
75
  Returns:
78
76
  Dict: The response from the API with message content.
@@ -80,8 +78,7 @@ class AcolytAskGpt(AcolytBaseTool):
80
78
  Raises:
81
79
  Exception: If there's an error accessing the Acolyt API.
82
80
  """
83
- context = self.context_from_config(config)
84
- api_key = self.get_api_key(context)
81
+ api_key = self.get_api_key()
85
82
  if not api_key:
86
83
  raise ValueError("Acolyt API key not found")
87
84
 
@@ -1,9 +1,10 @@
1
- from typing import Optional, Type
1
+ from typing import Type
2
2
 
3
+ from langchain.tools.base import ToolException
3
4
  from pydantic import BaseModel, Field
4
5
 
5
6
  from intentkit.abstracts.skill import SkillStoreABC
6
- from intentkit.skills.base import IntentKitSkill, SkillContext
7
+ from intentkit.skills.base import IntentKitSkill
7
8
 
8
9
  base_url = "https://acolyt-oracle-poc.vercel.app"
9
10
 
@@ -18,10 +19,19 @@ class AcolytBaseTool(IntentKitSkill):
18
19
  description="The skill store for persisting data"
19
20
  )
20
21
 
21
- def get_api_key(self, context: SkillContext) -> Optional[str]:
22
- if "api_key" in context.config and context.config["api_key"]:
23
- return context.config["api_key"]
24
- return self.skill_store.get_system_config("acolyt_api_key")
22
+ def get_api_key(self) -> str:
23
+ context = self.get_context()
24
+ skill_config = context.agent.skill_config(self.category)
25
+ api_key_provider = skill_config.get("api_key_provider")
26
+ if api_key_provider == "platform":
27
+ return self.skill_store.get_system_config("acolyt_api_key")
28
+ # for backward compatibility, may only have api_key in skill_config
29
+ elif skill_config.get("api_key"):
30
+ return skill_config.get("api_key")
31
+ else:
32
+ raise ToolException(
33
+ f"Invalid API key provider: {api_key_provider}, or no api_key in config"
34
+ )
25
35
 
26
36
  @property
27
37
  def category(self) -> str:
@@ -48,7 +48,6 @@ async def get_skills(
48
48
  get_aixbt_skill(
49
49
  name=name,
50
50
  store=store,
51
- api_key=config.get("api_key", ""),
52
51
  )
53
52
  for name in available_skills
54
53
  ]
@@ -57,17 +56,14 @@ async def get_skills(
57
56
  def get_aixbt_skill(
58
57
  name: str,
59
58
  store: SkillStoreABC,
60
- api_key: str = "",
61
59
  ) -> AIXBTBaseTool:
62
60
  """Get an AIXBT API skill by name."""
63
- cache_key = f"{name}:{api_key}"
64
61
 
65
62
  if name == "aixbt_projects":
66
- if cache_key not in _cache:
67
- _cache[cache_key] = AIXBTProjects(
63
+ if name not in _cache:
64
+ _cache[name] = AIXBTProjects(
68
65
  skill_store=store,
69
- api_key=api_key,
70
66
  )
71
- return _cache[cache_key]
67
+ return _cache[name]
72
68
  else:
73
69
  raise ValueError(f"Unknown AIXBT skill: {name}")
@@ -1,9 +1,8 @@
1
1
  import logging
2
- import os
3
2
  from typing import Any, Dict, Optional, Type
4
3
 
5
4
  import httpx
6
- from langchain_core.runnables import RunnableConfig
5
+ from langchain_core.tools import ToolException
7
6
  from pydantic import BaseModel, Field
8
7
 
9
8
  from intentkit.skills.aixbt.base import AIXBTBaseTool
@@ -55,11 +54,9 @@ class AIXBTProjects(AIXBTBaseTool):
55
54
  "for accessing AIXBT's specific dataset for crypto research."
56
55
  )
57
56
  args_schema: Type[BaseModel] = ProjectsInput
58
- api_key: str = ""
59
57
 
60
58
  async def _arun(
61
59
  self,
62
- config: RunnableConfig,
63
60
  limit: int = 10,
64
61
  name: Optional[str] = None,
65
62
  ticker: Optional[str] = None,
@@ -83,34 +80,27 @@ class AIXBTProjects(AIXBTBaseTool):
83
80
  JSON response with project data
84
81
  """
85
82
  # Get context from the config
86
- context = self.context_from_config(config)
83
+ context = self.get_context()
84
+ skill_config = context.agent.skill_config(self.category)
87
85
  logger.debug(f"aixbt_projects.py: Running search with context {context}")
88
86
 
89
87
  # Check for rate limiting if configured
90
- if context.config.get("rate_limit_number") and context.config.get(
88
+ if skill_config.get("rate_limit_number") and skill_config.get(
91
89
  "rate_limit_minutes"
92
90
  ):
93
91
  await self.user_rate_limit_by_category(
94
92
  context.user_id,
95
- context.config["rate_limit_number"],
96
- context.config["rate_limit_minutes"],
93
+ skill_config["rate_limit_number"],
94
+ skill_config["rate_limit_minutes"],
97
95
  )
98
96
 
99
97
  # Get the API key from the agent's configuration
100
- api_key = context.config.get("api_key")
98
+ api_key = skill_config.get("api_key")
101
99
 
102
- # If not available in config, try the instance attribute (for backward compatibility)
103
100
  if not api_key:
104
- api_key = self.api_key
105
-
106
- # If still not available, try environment variable as last resort
107
- if not api_key:
108
- api_key = os.environ.get("AIXBT_API_KEY")
109
-
110
- if not api_key:
111
- return {
112
- "error": "AIXBT API key is not available. Please provide it in the agent configuration."
113
- }
101
+ raise ToolException(
102
+ "AIXBT API key is not available. Please provide it in the agent configuration."
103
+ )
114
104
 
115
105
  base_url = "https://api.aixbt.tech/v1/projects"
116
106
 
@@ -134,20 +124,6 @@ class AIXBTProjects(AIXBTBaseTool):
134
124
  response = await client.get(base_url, params=params, headers=headers)
135
125
  response.raise_for_status()
136
126
  return response.json()
137
- except httpx.HTTPStatusError as e:
138
- logger.error(
139
- f"aixbt_projects.py: HTTP error occurred: {e.response.status_code} - {e.response.text}"
140
- )
141
- return {
142
- "error": f"HTTP error occurred: {e.response.status_code}",
143
- "details": e.response.text,
144
- }
145
- except httpx.RequestError as e:
146
- logger.error(f"aixbt_projects.py: Request error occurred: {str(e)}")
147
- return {"error": f"Request error occurred: {str(e)}"}
148
127
  except Exception as e:
149
- logger.error(
150
- f"aixbt_projects.py: An unexpected error occurred: {str(e)}",
151
- exc_info=True,
152
- )
153
- return {"error": f"An unexpected error occurred: {str(e)}"}
128
+ logger.error(f"Error getting projects: {str(e)}")
129
+ raise type(e)(f"[agent:{context.agent_id}]: {e}") from e
@@ -1,9 +1,10 @@
1
- from typing import Optional, Type
1
+ from typing import Type
2
2
 
3
+ from langchain.tools.base import ToolException
3
4
  from pydantic import BaseModel, Field
4
5
 
5
6
  from intentkit.abstracts.skill import SkillStoreABC
6
- from intentkit.skills.base import IntentKitSkill, SkillContext
7
+ from intentkit.skills.base import IntentKitSkill
7
8
 
8
9
  base_url = "https://api.upshot.xyz/v2/allora"
9
10
 
@@ -18,10 +19,19 @@ class AlloraBaseTool(IntentKitSkill):
18
19
  description="The skill store for persisting data"
19
20
  )
20
21
 
21
- def get_api_key(self, context: SkillContext) -> Optional[str]:
22
- if "api_key" in context.config and context.config["api_key"]:
23
- return context.config["api_key"]
24
- return self.skill_store.get_system_config("allora_api_key")
22
+ def get_api_key(self) -> str:
23
+ context = self.get_context()
24
+ skill_config = context.agent.skill_config(self.category)
25
+ api_key_provider = skill_config.get("api_key_provider")
26
+ if api_key_provider == "platform":
27
+ return self.skill_store.get_system_config("allora_api_key")
28
+ # for backward compatibility, may only have api_key in skill_config
29
+ elif skill_config.get("api_key"):
30
+ return skill_config.get("api_key")
31
+ else:
32
+ raise ToolException(
33
+ f"Invalid API key provider: {api_key_provider}, or no api_key in config"
34
+ )
25
35
 
26
36
  @property
27
37
  def category(self) -> str:
@@ -2,7 +2,6 @@ from typing import Literal, Type
2
2
 
3
3
  import httpx
4
4
  from langchain.tools.base import ToolException
5
- from langchain_core.runnables import RunnableConfig
6
5
  from pydantic import BaseModel, Field
7
6
 
8
7
  from intentkit.skills.allora.base import AlloraBaseTool
@@ -84,7 +83,7 @@ class AlloraGetPrice(AlloraBaseTool):
84
83
  raise NotImplementedError("Use _arun instead")
85
84
 
86
85
  async def _arun(
87
- self, token: str, time_frame: str, config: RunnableConfig, **kwargs
86
+ self, token: str, time_frame: str, **kwargs
88
87
  ) -> AlloraGetPriceOutput:
89
88
  """Run the tool to get the token price prediction from Allora API.
90
89
  Args:
@@ -98,8 +97,7 @@ class AlloraGetPrice(AlloraBaseTool):
98
97
  Raises:
99
98
  Exception: If there's an error accessing the Allora API.
100
99
  """
101
- context = self.context_from_config(config)
102
- api_key = self.get_api_key(context)
100
+ api_key = self.get_api_key()
103
101
  if not api_key:
104
102
  raise ValueError("Allora API key not found")
105
103
 
intentkit/skills/base.py CHANGED
@@ -5,6 +5,7 @@ from typing import Any, Callable, Dict, Literal, NotRequired, Optional, TypedDic
5
5
  from langchain_core.runnables import RunnableConfig
6
6
  from langchain_core.tools import BaseTool
7
7
  from langchain_core.tools.base import ToolException
8
+ from langgraph.runtime import get_runtime
8
9
  from pydantic import (
9
10
  BaseModel,
10
11
  ValidationError,
@@ -13,12 +14,14 @@ from pydantic.v1 import ValidationError as ValidationErrorV1
13
14
  from redis.exceptions import RedisError
14
15
 
15
16
  from intentkit.abstracts.exception import RateLimitExceeded
17
+ from intentkit.abstracts.graph import AgentContext
16
18
  from intentkit.abstracts.skill import SkillStoreABC
17
19
  from intentkit.models.agent import Agent
18
20
  from intentkit.models.redis import get_redis
19
21
 
20
22
  SkillState = Literal["disabled", "public", "private"]
21
23
  SkillOwnerState = Literal["disabled", "private"]
24
+ APIKeyProviderValue = Literal["platform", "agent_owner"]
22
25
 
23
26
 
24
27
  class SkillConfig(TypedDict):
@@ -26,7 +29,7 @@ class SkillConfig(TypedDict):
26
29
 
27
30
  enabled: bool
28
31
  states: Dict[str, SkillState | SkillOwnerState]
29
- api_key_provider: NotRequired[str]
32
+ api_key_provider: NotRequired[APIKeyProviderValue]
30
33
  __extra__: NotRequired[Dict[str, Any]]
31
34
 
32
35
 
@@ -192,3 +195,7 @@ class IntentKitSkill(BaseTool):
192
195
  entrypoint=configurable.get("entrypoint"),
193
196
  is_private=configurable.get("is_private"),
194
197
  )
198
+
199
+ def get_context(self) -> AgentContext:
200
+ runtime = get_runtime(AgentContext)
201
+ return runtime.context
@@ -2,10 +2,11 @@ import logging
2
2
  from typing import Any, Dict, Optional, Tuple, Type
3
3
 
4
4
  import httpx # Ensure httpx is installed: pip install httpx
5
+ from langchain.tools.base import ToolException
5
6
  from pydantic import BaseModel, Field
6
7
 
7
8
  from intentkit.abstracts.skill import SkillStoreABC
8
- from intentkit.skills.base import IntentKitSkill, SkillContext, ToolException
9
+ from intentkit.skills.base import IntentKitSkill
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
@@ -24,7 +25,7 @@ class CarvBaseTool(IntentKitSkill):
24
25
  def category(self) -> str:
25
26
  return "carv"
26
27
 
27
- def get_api_key(self, context: SkillContext) -> str:
28
+ def get_api_key(self) -> str:
28
29
  """
29
30
  Retrieves the CARV API key based on the api_key_provider setting.
30
31
 
@@ -35,10 +36,11 @@ class CarvBaseTool(IntentKitSkill):
35
36
  ToolException: If the API key is not found or provider is invalid.
36
37
  """
37
38
  try:
38
- skillConfig = context.config
39
- api_key_provider = skillConfig.get("api_key_provider")
39
+ context = self.get_context()
40
+ skill_config = context.agent.skill_config(self.category)
41
+ api_key_provider = skill_config.get("api_key_provider")
40
42
  if api_key_provider == "agent_owner":
41
- agent_api_key: Optional[str] = context.config.get("api_key")
43
+ agent_api_key: Optional[str] = skill_config.get("api_key")
42
44
  if agent_api_key:
43
45
  logger.debug(
44
46
  f"Using agent-specific CARV API key for skill {self.name} in category {self.category}"
@@ -70,15 +72,15 @@ class CarvBaseTool(IntentKitSkill):
70
72
  raise
71
73
  raise ToolException(f"Failed to retrieve CARV API key: {str(e)}") from e
72
74
 
73
- async def apply_rate_limit(self, context: SkillContext) -> None:
75
+ async def apply_rate_limit(self, context) -> None:
74
76
  """
75
77
  Applies rate limiting ONLY if specified in the agent's config ('skill_config').
76
78
  Checks for 'rate_limit_number' and 'rate_limit_minutes'.
77
79
  If not configured, NO rate limiting is applied.
78
80
  Raises ConnectionAbortedError if the configured limit is exceeded.
79
81
  """
80
- skill_config = context.config
81
- user_id = context.user_id
82
+ skill_config = context.agent.skill_config(self.category)
83
+ user_id = context.agent.id
82
84
 
83
85
  limit_num = skill_config.get("rate_limit_number")
84
86
  limit_min = skill_config.get("rate_limit_minutes")
@@ -98,7 +100,7 @@ class CarvBaseTool(IntentKitSkill):
98
100
 
99
101
  async def _call_carv_api(
100
102
  self,
101
- context: SkillContext,
103
+ context,
102
104
  endpoint: str,
103
105
  method: str = "GET",
104
106
  params: Optional[Dict[str, Any]] = None,
@@ -122,7 +124,7 @@ class CarvBaseTool(IntentKitSkill):
122
124
  url = f"{CARV_API_BASE_URL}{endpoint}"
123
125
 
124
126
  try:
125
- api_key = self.get_api_key(context)
127
+ api_key = self.get_api_key()
126
128
 
127
129
  headers = {
128
130
  "Authorization": api_key,
@@ -1,92 +1,90 @@
1
- import logging
2
- from typing import Any, Dict, Type
3
-
4
- from langchain_core.runnables import RunnableConfig
5
- from pydantic import BaseModel
6
-
7
- from intentkit.skills.carv.base import CarvBaseTool
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
-
12
- class CarvNewsInput(BaseModel):
13
- """
14
- Input schema for CARV News API.
15
- This API endpoint does not require any specific parameters from the user.
16
- """
17
-
18
- pass
19
-
20
-
21
- class FetchNewsTool(CarvBaseTool):
22
- """
23
- Tool for fetching the latest news articles from the CARV API.
24
- This tool retrieves a list of recent news items, each including a title, URL, and a short description (card_text).
25
- It's useful for getting up-to-date information on various topics covered by CARV's news aggregation.
26
- """
27
-
28
- name: str = "carv_fetch_news"
29
- description: str = (
30
- "Fetches the latest news articles from the CARV API. "
31
- "Returns a list of news items, each with a title, URL, and a short summary (card_text)."
32
- )
33
- args_schema: Type[BaseModel] = CarvNewsInput
34
-
35
- async def _arun(
36
- self,
37
- config: RunnableConfig = None, # type: ignore
38
- **kwargs: Any,
39
- ) -> Dict[str, Any]:
40
- """
41
- Fetches news from the CARV API and returns the response.
42
- The expected successful response structure is a dictionary containing an "infos" key,
43
- which holds a list of news articles.
44
- Example: {"infos": [{"title": "...", "url": "...", "card_text": "..."}, ...]}
45
- """
46
- context = self.context_from_config(config)
47
-
48
- try:
49
- await self.apply_rate_limit(context)
50
-
51
- result, error = await self._call_carv_api(
52
- context=context,
53
- endpoint="/ai-agent-backend/news",
54
- method="GET",
55
- )
56
-
57
- if error is not None or result is None:
58
- logger.error(f"Error returned from CARV API (News): {error}")
59
- return {
60
- "error": True,
61
- "error_type": "APIError",
62
- "message": "Failed to fetch news from CARV API.",
63
- "details": error, # error is the detailed error dict from _call_carv_api
64
- }
65
-
66
- # _call_carv_api returns response_json.get("data", response_json) on success.
67
- # For this endpoint, the "data" field should be {"infos": [...]}.
68
- # So, 'result' should be {"infos": [...]}.
69
- if "infos" not in result or not isinstance(result.get("infos"), list):
70
- logger.warning(
71
- f"CARV API (News) response did not contain 'infos' list as expected: {result}"
72
- )
73
- return {
74
- "error": True,
75
- "error_type": "UnexpectedResponseFormat",
76
- "message": "News data from CARV API is missing the 'infos' list or has incorrect format.",
77
- "details": result,
78
- }
79
-
80
- # Successfully fetched and validated news data
81
- return result # This will be {"infos": [...]}
82
-
83
- except Exception as e:
84
- logger.error(
85
- f"An unexpected error occurred while fetching news: {e}", exc_info=True
86
- )
87
- return {
88
- "error": True,
89
- "error_type": type(e).__name__,
90
- "message": "An unexpected error occurred while processing the news request.",
91
- "details": str(e),
92
- }
1
+ import logging
2
+ from typing import Any, Dict, Type
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from intentkit.skills.carv.base import CarvBaseTool
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class CarvNewsInput(BaseModel):
12
+ """
13
+ Input schema for CARV News API.
14
+ This API endpoint does not require any specific parameters from the user.
15
+ """
16
+
17
+ pass
18
+
19
+
20
+ class FetchNewsTool(CarvBaseTool):
21
+ """
22
+ Tool for fetching the latest news articles from the CARV API.
23
+ This tool retrieves a list of recent news items, each including a title, URL, and a short description (card_text).
24
+ It's useful for getting up-to-date information on various topics covered by CARV's news aggregation.
25
+ """
26
+
27
+ name: str = "carv_fetch_news"
28
+ description: str = (
29
+ "Fetches the latest news articles from the CARV API. "
30
+ "Returns a list of news items, each with a title, URL, and a short summary (card_text)."
31
+ )
32
+ args_schema: Type[BaseModel] = CarvNewsInput
33
+
34
+ async def _arun(
35
+ self, # type: ignore
36
+ **kwargs: Any,
37
+ ) -> Dict[str, Any]:
38
+ """
39
+ Fetches news from the CARV API and returns the response.
40
+ The expected successful response structure is a dictionary containing an "infos" key,
41
+ which holds a list of news articles.
42
+ Example: {"infos": [{"title": "...", "url": "...", "card_text": "..."}, ...]}
43
+ """
44
+ context = self.get_context()
45
+
46
+ try:
47
+ await self.apply_rate_limit(context)
48
+
49
+ result, error = await self._call_carv_api(
50
+ context=context,
51
+ endpoint="/ai-agent-backend/news",
52
+ method="GET",
53
+ )
54
+
55
+ if error is not None or result is None:
56
+ logger.error(f"Error returned from CARV API (News): {error}")
57
+ return {
58
+ "error": True,
59
+ "error_type": "APIError",
60
+ "message": "Failed to fetch news from CARV API.",
61
+ "details": error, # error is the detailed error dict from _call_carv_api
62
+ }
63
+
64
+ # _call_carv_api returns response_json.get("data", response_json) on success.
65
+ # For this endpoint, the "data" field should be {"infos": [...]}.
66
+ # So, 'result' should be {"infos": [...]}.
67
+ if "infos" not in result or not isinstance(result.get("infos"), list):
68
+ logger.warning(
69
+ f"CARV API (News) response did not contain 'infos' list as expected: {result}"
70
+ )
71
+ return {
72
+ "error": True,
73
+ "error_type": "UnexpectedResponseFormat",
74
+ "message": "News data from CARV API is missing the 'infos' list or has incorrect format.",
75
+ "details": result,
76
+ }
77
+
78
+ # Successfully fetched and validated news data
79
+ return result # This will be {"infos": [...]}
80
+
81
+ except Exception as e:
82
+ logger.error(
83
+ f"An unexpected error occurred while fetching news: {e}", exc_info=True
84
+ )
85
+ return {
86
+ "error": True,
87
+ "error_type": type(e).__name__,
88
+ "message": "An unexpected error occurred while processing the news request.",
89
+ "details": str(e),
90
+ }