intentkit 0.6.9.dev1__py3-none-any.whl → 0.6.10.dev1__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 (168) hide show
  1. intentkit/__init__.py +1 -1
  2. intentkit/abstracts/graph.py +17 -2
  3. intentkit/core/engine.py +29 -30
  4. intentkit/core/node.py +10 -20
  5. intentkit/models/agent.py +3 -0
  6. intentkit/models/chat.py +9 -1
  7. intentkit/skills/acolyt/ask.py +2 -5
  8. intentkit/skills/acolyt/base.py +16 -6
  9. intentkit/skills/aixbt/__init__.py +3 -7
  10. intentkit/skills/aixbt/projects.py +12 -36
  11. intentkit/skills/allora/base.py +16 -6
  12. intentkit/skills/allora/price.py +2 -4
  13. intentkit/skills/base.py +8 -1
  14. intentkit/skills/carv/base.py +12 -10
  15. intentkit/skills/carv/fetch_news.py +90 -92
  16. intentkit/skills/carv/onchain_query.py +162 -164
  17. intentkit/skills/carv/token_info_and_price.py +108 -110
  18. intentkit/skills/chainlist/chain_lookup.py +1 -2
  19. intentkit/skills/common/current_time.py +1 -2
  20. intentkit/skills/cookiefun/base.py +20 -12
  21. intentkit/skills/cookiefun/get_account_details.py +1 -3
  22. intentkit/skills/cookiefun/get_account_feed.py +1 -3
  23. intentkit/skills/cookiefun/get_account_smart_followers.py +1 -3
  24. intentkit/skills/cookiefun/get_sectors.py +2 -3
  25. intentkit/skills/cookiefun/search_accounts.py +1 -3
  26. intentkit/skills/cryptocompare/fetch_news.py +3 -4
  27. intentkit/skills/cryptocompare/fetch_price.py +3 -4
  28. intentkit/skills/cryptocompare/fetch_top_exchanges.py +3 -4
  29. intentkit/skills/cryptocompare/fetch_top_market_cap.py +3 -4
  30. intentkit/skills/cryptocompare/fetch_top_volume.py +3 -4
  31. intentkit/skills/cryptocompare/fetch_trading_signals.py +3 -4
  32. intentkit/skills/cryptopanic/base.py +13 -9
  33. intentkit/skills/cryptopanic/fetch_crypto_news.py +150 -153
  34. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +133 -136
  35. intentkit/skills/dapplooker/base.py +16 -6
  36. intentkit/skills/dapplooker/dapplooker_token_data.py +2 -4
  37. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +2 -3
  38. intentkit/skills/defillama/coins/fetch_block.py +2 -3
  39. intentkit/skills/defillama/coins/fetch_current_prices.py +2 -5
  40. intentkit/skills/defillama/coins/fetch_first_price.py +2 -5
  41. intentkit/skills/defillama/coins/fetch_historical_prices.py +2 -3
  42. intentkit/skills/defillama/coins/fetch_price_chart.py +2 -5
  43. intentkit/skills/defillama/coins/fetch_price_percentage.py +2 -5
  44. intentkit/skills/defillama/fees/fetch_fees_overview.py +2 -3
  45. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +2 -3
  46. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +2 -3
  47. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +2 -3
  48. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +2 -3
  49. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -5
  50. intentkit/skills/defillama/tvl/fetch_chains.py +2 -3
  51. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -3
  52. intentkit/skills/defillama/tvl/fetch_protocol.py +2 -5
  53. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +2 -5
  54. intentkit/skills/defillama/tvl/fetch_protocols.py +2 -3
  55. intentkit/skills/defillama/volumes/fetch_dex_overview.py +2 -3
  56. intentkit/skills/defillama/volumes/fetch_dex_summary.py +2 -5
  57. intentkit/skills/defillama/volumes/fetch_options_overview.py +2 -3
  58. intentkit/skills/defillama/yields/fetch_pool_chart.py +2 -5
  59. intentkit/skills/defillama/yields/fetch_pools.py +2 -3
  60. intentkit/skills/dune_analytics/base.py +15 -9
  61. intentkit/skills/dune_analytics/fetch_kol_buys.py +125 -128
  62. intentkit/skills/dune_analytics/fetch_nation_metrics.py +234 -237
  63. intentkit/skills/elfa/base.py +16 -6
  64. intentkit/skills/elfa/mention.py +2 -7
  65. intentkit/skills/elfa/stats.py +2 -6
  66. intentkit/skills/elfa/tokens.py +1 -4
  67. intentkit/skills/enso/base.py +25 -13
  68. intentkit/skills/enso/best_yield.py +1 -4
  69. intentkit/skills/enso/networks.py +2 -5
  70. intentkit/skills/enso/prices.py +1 -5
  71. intentkit/skills/enso/route.py +2 -5
  72. intentkit/skills/enso/tokens.py +1 -4
  73. intentkit/skills/enso/wallet.py +3 -9
  74. intentkit/skills/firecrawl/base.py +16 -6
  75. intentkit/skills/firecrawl/clear.py +1 -3
  76. intentkit/skills/firecrawl/crawl.py +7 -8
  77. intentkit/skills/firecrawl/query.py +7 -9
  78. intentkit/skills/firecrawl/scrape.py +7 -8
  79. intentkit/skills/github/github_search.py +1 -3
  80. intentkit/skills/heurist/base.py +15 -0
  81. intentkit/skills/heurist/image_generation_animagine_xl.py +3 -4
  82. intentkit/skills/heurist/image_generation_arthemy_comics.py +3 -4
  83. intentkit/skills/heurist/image_generation_arthemy_real.py +3 -4
  84. intentkit/skills/heurist/image_generation_braindance.py +3 -4
  85. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +3 -4
  86. intentkit/skills/heurist/image_generation_flux_1_dev.py +3 -4
  87. intentkit/skills/heurist/image_generation_sdxl.py +3 -4
  88. intentkit/skills/http/get.py +0 -2
  89. intentkit/skills/http/post.py +0 -2
  90. intentkit/skills/http/put.py +0 -2
  91. intentkit/skills/lifi/token_execute.py +1 -3
  92. intentkit/skills/lifi/token_quote.py +0 -2
  93. intentkit/skills/moralis/base.py +15 -1
  94. intentkit/skills/nation/nft_check.py +2 -5
  95. intentkit/skills/openai/base.py +14 -5
  96. intentkit/skills/openai/dalle_image_generation.py +6 -5
  97. intentkit/skills/openai/gpt_image_generation.py +6 -5
  98. intentkit/skills/openai/gpt_image_to_image.py +6 -5
  99. intentkit/skills/openai/image_to_text.py +6 -6
  100. intentkit/skills/portfolio/base.py +4 -3
  101. intentkit/skills/portfolio/token_balances.py +2 -4
  102. intentkit/skills/portfolio/wallet_approvals.py +2 -4
  103. intentkit/skills/portfolio/wallet_defi_positions.py +3 -4
  104. intentkit/skills/portfolio/wallet_history.py +2 -4
  105. intentkit/skills/portfolio/wallet_net_worth.py +2 -4
  106. intentkit/skills/portfolio/wallet_nfts.py +2 -4
  107. intentkit/skills/portfolio/wallet_profitability.py +2 -4
  108. intentkit/skills/portfolio/wallet_profitability_summary.py +2 -4
  109. intentkit/skills/portfolio/wallet_stats.py +2 -4
  110. intentkit/skills/portfolio/wallet_swaps.py +2 -4
  111. intentkit/skills/slack/base.py +18 -0
  112. intentkit/skills/slack/get_channel.py +3 -4
  113. intentkit/skills/slack/get_message.py +3 -4
  114. intentkit/skills/slack/schedule_message.py +3 -4
  115. intentkit/skills/slack/send_message.py +3 -4
  116. intentkit/skills/supabase/delete_data.py +3 -6
  117. intentkit/skills/supabase/fetch_data.py +3 -6
  118. intentkit/skills/supabase/insert_data.py +3 -6
  119. intentkit/skills/supabase/invoke_function.py +3 -6
  120. intentkit/skills/supabase/update_data.py +3 -6
  121. intentkit/skills/supabase/upsert_data.py +3 -6
  122. intentkit/skills/system/add_autonomous_task.py +1 -3
  123. intentkit/skills/system/delete_autonomous_task.py +1 -3
  124. intentkit/skills/system/edit_autonomous_task.py +1 -3
  125. intentkit/skills/system/list_autonomous_tasks.py +1 -3
  126. intentkit/skills/system/read_agent_api_key.py +2 -3
  127. intentkit/skills/system/regenerate_agent_api_key.py +2 -5
  128. intentkit/skills/tavily/base.py +14 -5
  129. intentkit/skills/tavily/tavily_extract.py +7 -8
  130. intentkit/skills/tavily/tavily_search.py +11 -9
  131. intentkit/skills/token/base.py +4 -6
  132. intentkit/skills/token/erc20_transfers.py +2 -4
  133. intentkit/skills/token/token_analytics.py +2 -4
  134. intentkit/skills/token/token_price.py +2 -4
  135. intentkit/skills/token/token_search.py +2 -4
  136. intentkit/skills/twitter/base.py +41 -0
  137. intentkit/skills/twitter/follow_user.py +4 -4
  138. intentkit/skills/twitter/get_mentions.py +4 -4
  139. intentkit/skills/twitter/get_timeline.py +4 -4
  140. intentkit/skills/twitter/get_user_by_username.py +4 -4
  141. intentkit/skills/twitter/get_user_tweets.py +4 -4
  142. intentkit/skills/twitter/like_tweet.py +4 -4
  143. intentkit/skills/twitter/post_tweet.py +3 -4
  144. intentkit/skills/twitter/reply_tweet.py +3 -4
  145. intentkit/skills/twitter/retweet.py +4 -4
  146. intentkit/skills/twitter/search_tweets.py +4 -4
  147. intentkit/skills/unrealspeech/base.py +16 -0
  148. intentkit/skills/unrealspeech/text_to_speech.py +4 -4
  149. intentkit/skills/venice_audio/base.py +11 -9
  150. intentkit/skills/venice_audio/venice_audio.py +238 -240
  151. intentkit/skills/venice_image/base.py +23 -19
  152. intentkit/skills/venice_image/image_enhance/image_enhance.py +78 -80
  153. intentkit/skills/venice_image/image_generation/image_generation_base.py +115 -117
  154. intentkit/skills/venice_image/image_upscale/image_upscale.py +88 -90
  155. intentkit/skills/venice_image/image_vision/image_vision.py +98 -100
  156. intentkit/skills/web_scraper/document_indexer.py +3 -5
  157. intentkit/skills/web_scraper/scrape_and_index.py +14 -17
  158. intentkit/skills/web_scraper/website_indexer.py +8 -10
  159. intentkit/skills/xmtp/README.md +110 -0
  160. intentkit/skills/xmtp/__init__.py +82 -0
  161. intentkit/skills/xmtp/base.py +13 -0
  162. intentkit/skills/xmtp/schema.json +41 -0
  163. intentkit/skills/xmtp/transfer.py +170 -0
  164. intentkit/skills/xmtp/xmtp.svg +26 -0
  165. {intentkit-0.6.9.dev1.dist-info → intentkit-0.6.10.dev1.dist-info}/METADATA +3 -3
  166. {intentkit-0.6.9.dev1.dist-info → intentkit-0.6.10.dev1.dist-info}/RECORD +168 -162
  167. {intentkit-0.6.9.dev1.dist-info → intentkit-0.6.10.dev1.dist-info}/WHEEL +0 -0
  168. {intentkit-0.6.9.dev1.dist-info → intentkit-0.6.10.dev1.dist-info}/licenses/LICENSE +0 -0
@@ -1,240 +1,238 @@
1
- import hashlib
2
- import json
3
- import logging
4
- from typing import Any, Dict, Optional, Type
5
-
6
- import httpx
7
- from langchain_core.runnables import RunnableConfig
8
- from pydantic import BaseModel, Field
9
-
10
- from intentkit.abstracts.skill import SkillStoreABC
11
- from intentkit.skills.venice_audio.base import VeniceAudioBaseTool
12
- from intentkit.skills.venice_audio.input import AllowedAudioFormat, VeniceAudioInput
13
- from intentkit.utils.s3 import FileType, store_file_bytes
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
- base_url = "https://api.venice.ai"
18
-
19
-
20
- class VeniceAudioTool(VeniceAudioBaseTool):
21
- """
22
- Tool for generating audio using the Venice AI Text-to-Speech API (/audio/speech).
23
- It requires a specific 'voice_model' to be configured for the instance.
24
- Handles API calls, rate limiting, storage, and returns results or API errors as dictionaries.
25
-
26
- On successful audio generation, returns a dictionary with audio details.
27
- On Venice API error (non-200 status), returns a dictionary containing
28
- the error details from the API response instead of raising an exception.
29
- """
30
-
31
- name: str = "venice_audio_text_to_speech"
32
- description: str = (
33
- "Converts text to speech using a configured Venice AI voice model. "
34
- "Requires input text. Optional parameters include speed (0.25-4.0, default 1.0) "
35
- "and audio format (mp3, opus, aac, flac, wav, pcm, default mp3)."
36
- )
37
- args_schema: Type[BaseModel] = VeniceAudioInput
38
- skill_store: SkillStoreABC = Field(
39
- description="The skill store instance for accessing system/agent configurations and persisting data."
40
- )
41
-
42
- async def _arun(
43
- self,
44
- input: str,
45
- voice_model: str,
46
- config: RunnableConfig,
47
- speed: Optional[float] = 1.0,
48
- response_format: Optional[AllowedAudioFormat] = "mp3",
49
- **kwargs, # type: ignore
50
- ) -> Dict[str, Any]:
51
- """
52
- Generates audio using the configured voice model via Venice AI TTS /audio/speech endpoint.
53
- Stores the resulting audio using store_file_bytes.
54
- Returns a dictionary containing audio details on success, or API error details on failure.
55
- """
56
- context = self.context_from_config(config)
57
- final_response_format = response_format if response_format else "mp3"
58
- tts_model_id = "tts-kokoro" # API model used
59
-
60
- try:
61
- # --- Setup Checks ---
62
- api_key = self.get_api_key(context)
63
-
64
- _, error_info = self.validate_voice_model(context, voice_model)
65
- if error_info:
66
- return error_info
67
-
68
- if not api_key:
69
- message = (
70
- f"Venice AI API key configuration missing for skill '{self.name}'."
71
- )
72
- details = f"API key not found for category '{self.category}'. Please configure it."
73
- logger.error(message)
74
- return {
75
- "error": True,
76
- "error_type": "ConfigurationError",
77
- "message": message,
78
- "details": details,
79
- "voice_model": voice_model,
80
- "requested_format": final_response_format,
81
- }
82
-
83
- if not voice_model:
84
- message = (
85
- f"Instance of {self.name} was created without a 'voice_model'."
86
- )
87
- details = "Voice model must be specified for this tool instance."
88
- logger.error(message)
89
- return {
90
- "error": True,
91
- "error_type": "ConfigurationError",
92
- "message": message,
93
- "details": details,
94
- "voice_model": voice_model,
95
- "requested_format": final_response_format,
96
- }
97
-
98
- await self.apply_rate_limit(context)
99
-
100
- # --- Prepare API Call ---
101
- payload: Dict[str, Any] = {
102
- "model": tts_model_id,
103
- "input": input,
104
- "voice": voice_model,
105
- "response_format": final_response_format,
106
- "speed": speed if speed is not None else 1.0,
107
- "streaming": False,
108
- }
109
-
110
- payload = {k: v for k, v in payload.items() if v is not None}
111
-
112
- logger.debug(
113
- f"Venice Audio API Call: Voice='{voice_model}', Format='{final_response_format}', Payload='{payload}'"
114
- )
115
-
116
- headers = {
117
- "Authorization": f"Bearer {api_key}",
118
- "Content-Type": "application/json",
119
- }
120
- api_url = f"{base_url}/api/v1/audio/speech"
121
-
122
- # --- Execute API Call ---
123
- async with httpx.AsyncClient(timeout=120.0) as client:
124
- response = await client.post(api_url, json=payload, headers=headers)
125
- logger.debug(
126
- f"Venice Audio API Response: Voice='{voice_model}', Format='{final_response_format}', Status={response.status_code}"
127
- )
128
-
129
- content_type_header = str(
130
- response.headers.get("content-type", "")
131
- ).lower()
132
-
133
- # --- Handle API Success or Error from Response Body ---
134
- if response.status_code == 200 and content_type_header.startswith(
135
- "audio/"
136
- ):
137
- audio_bytes = response.content
138
- if not audio_bytes:
139
- message = (
140
- "API returned success status but response body was empty."
141
- )
142
- logger.warning(
143
- f"Venice Audio API (Voice: {voice_model}) returned 200 OK but empty audio content."
144
- )
145
- return {
146
- "error": True,
147
- "error_type": "NoContentError",
148
- "message": message,
149
- "status_code": response.status_code,
150
- "voice_model": voice_model,
151
- "requested_format": final_response_format,
152
- }
153
-
154
- # --- Store Audio ---
155
- file_extension = final_response_format
156
- audio_hash = hashlib.sha256(audio_bytes).hexdigest()
157
- key = f"{self.category}/{voice_model}/{audio_hash}.{file_extension}"
158
-
159
- size_limit = 1024 * 20 # 20Mb Size limit
160
- stored_url = await store_file_bytes(
161
- file_bytes=audio_bytes,
162
- key=key,
163
- file_type=FileType.AUDIO,
164
- size_limit_bytes=size_limit,
165
- )
166
-
167
- if not stored_url:
168
- message = "Failed to store audio: S3 storage is not configured."
169
- logger.error(
170
- f"Failed to store audio (Voice: {voice_model}): S3 storage is not configured."
171
- )
172
- return {
173
- "error": True,
174
- "error_type": "StorageConfigurationError",
175
- "message": message,
176
- "voice_model": voice_model,
177
- "requested_format": final_response_format,
178
- }
179
-
180
- logger.info(
181
- f"Venice TTS success: Voice='{voice_model}', Format='{final_response_format}', Stored='{stored_url}'"
182
- )
183
- # --- Return Success Dictionary ---
184
- return {
185
- "audio_url": stored_url,
186
- "audio_bytes_sha256": audio_hash,
187
- "content_type": content_type_header,
188
- "voice_model": voice_model,
189
- "tts_engine": tts_model_id,
190
- "speed": speed if speed is not None else 1.0,
191
- "response_format": final_response_format,
192
- "input_text_length": len(input),
193
- "error": False,
194
- "status_code": response.status_code,
195
- }
196
- else:
197
- # Non-200 API response or non-audio content
198
- error_details: Any = f"Raw error response text: {response.text}"
199
- try:
200
- parsed_details = response.json()
201
- error_details = parsed_details
202
- except json.JSONDecodeError:
203
- pass # Keep raw text if JSON parsing fails
204
-
205
- message = "Venice Audio API returned a non-success status or unexpected content type."
206
- logger.error(
207
- f"Venice Audio API Error: Voice='{voice_model}', Format='{final_response_format}', Status={response.status_code}, Details: {error_details}"
208
- )
209
- return {
210
- "error": True,
211
- "error_type": "APIError",
212
- "message": message,
213
- "status_code": response.status_code,
214
- "details": error_details,
215
- "voice_model": voice_model,
216
- "requested_format": final_response_format,
217
- }
218
-
219
- except Exception as e:
220
- # Global exception handling for any uncaught error
221
- error_type = type(
222
- e
223
- ).__name__ # Gets the class name of the exception (e.g., 'TimeoutException', 'ToolException')
224
- message = f"An unexpected error occurred during audio generation for voice {voice_model}."
225
- details = str(e) # The string representation of the exception
226
-
227
- # Log the error with full traceback for debugging
228
- logger.error(
229
- f"Venice Audio Tool Global Error ({error_type}): {message} | Details: {details}",
230
- exc_info=True,
231
- )
232
-
233
- return {
234
- "error": True,
235
- "error_type": error_type, # e.g., "TimeoutException", "ToolException", "ClientError", "ValueError"
236
- "message": message,
237
- "details": details,
238
- "voice_model": voice_model,
239
- "requested_format": final_response_format,
240
- }
1
+ import hashlib
2
+ import json
3
+ import logging
4
+ from typing import Any, Dict, Optional, Type
5
+
6
+ import httpx
7
+ from pydantic import BaseModel, Field
8
+
9
+ from intentkit.abstracts.skill import SkillStoreABC
10
+ from intentkit.skills.venice_audio.base import VeniceAudioBaseTool
11
+ from intentkit.skills.venice_audio.input import AllowedAudioFormat, VeniceAudioInput
12
+ from intentkit.utils.s3 import FileType, store_file_bytes
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ base_url = "https://api.venice.ai"
17
+
18
+
19
+ class VeniceAudioTool(VeniceAudioBaseTool):
20
+ """
21
+ Tool for generating audio using the Venice AI Text-to-Speech API (/audio/speech).
22
+ It requires a specific 'voice_model' to be configured for the instance.
23
+ Handles API calls, rate limiting, storage, and returns results or API errors as dictionaries.
24
+
25
+ On successful audio generation, returns a dictionary with audio details.
26
+ On Venice API error (non-200 status), returns a dictionary containing
27
+ the error details from the API response instead of raising an exception.
28
+ """
29
+
30
+ name: str = "venice_audio_text_to_speech"
31
+ description: str = (
32
+ "Converts text to speech using a configured Venice AI voice model. "
33
+ "Requires input text. Optional parameters include speed (0.25-4.0, default 1.0) "
34
+ "and audio format (mp3, opus, aac, flac, wav, pcm, default mp3)."
35
+ )
36
+ args_schema: Type[BaseModel] = VeniceAudioInput
37
+ skill_store: SkillStoreABC = Field(
38
+ description="The skill store instance for accessing system/agent configurations and persisting data."
39
+ )
40
+
41
+ async def _arun(
42
+ self,
43
+ input: str,
44
+ voice_model: str,
45
+ speed: Optional[float] = 1.0,
46
+ response_format: Optional[AllowedAudioFormat] = "mp3",
47
+ **kwargs, # type: ignore
48
+ ) -> Dict[str, Any]:
49
+ """
50
+ Generates audio using the configured voice model via Venice AI TTS /audio/speech endpoint.
51
+ Stores the resulting audio using store_file_bytes.
52
+ Returns a dictionary containing audio details on success, or API error details on failure.
53
+ """
54
+ context = self.get_context()
55
+ final_response_format = response_format if response_format else "mp3"
56
+ tts_model_id = "tts-kokoro" # API model used
57
+
58
+ try:
59
+ # --- Setup Checks ---
60
+ api_key = self.get_api_key()
61
+
62
+ _, error_info = self.validate_voice_model(context, voice_model)
63
+ if error_info:
64
+ return error_info
65
+
66
+ if not api_key:
67
+ message = (
68
+ f"Venice AI API key configuration missing for skill '{self.name}'."
69
+ )
70
+ details = f"API key not found for category '{self.category}'. Please configure it."
71
+ logger.error(message)
72
+ return {
73
+ "error": True,
74
+ "error_type": "ConfigurationError",
75
+ "message": message,
76
+ "details": details,
77
+ "voice_model": voice_model,
78
+ "requested_format": final_response_format,
79
+ }
80
+
81
+ if not voice_model:
82
+ message = (
83
+ f"Instance of {self.name} was created without a 'voice_model'."
84
+ )
85
+ details = "Voice model must be specified for this tool instance."
86
+ logger.error(message)
87
+ return {
88
+ "error": True,
89
+ "error_type": "ConfigurationError",
90
+ "message": message,
91
+ "details": details,
92
+ "voice_model": voice_model,
93
+ "requested_format": final_response_format,
94
+ }
95
+
96
+ await self.apply_rate_limit(context)
97
+
98
+ # --- Prepare API Call ---
99
+ payload: Dict[str, Any] = {
100
+ "model": tts_model_id,
101
+ "input": input,
102
+ "voice": voice_model,
103
+ "response_format": final_response_format,
104
+ "speed": speed if speed is not None else 1.0,
105
+ "streaming": False,
106
+ }
107
+
108
+ payload = {k: v for k, v in payload.items() if v is not None}
109
+
110
+ logger.debug(
111
+ f"Venice Audio API Call: Voice='{voice_model}', Format='{final_response_format}', Payload='{payload}'"
112
+ )
113
+
114
+ headers = {
115
+ "Authorization": f"Bearer {api_key}",
116
+ "Content-Type": "application/json",
117
+ }
118
+ api_url = f"{base_url}/api/v1/audio/speech"
119
+
120
+ # --- Execute API Call ---
121
+ async with httpx.AsyncClient(timeout=120.0) as client:
122
+ response = await client.post(api_url, json=payload, headers=headers)
123
+ logger.debug(
124
+ f"Venice Audio API Response: Voice='{voice_model}', Format='{final_response_format}', Status={response.status_code}"
125
+ )
126
+
127
+ content_type_header = str(
128
+ response.headers.get("content-type", "")
129
+ ).lower()
130
+
131
+ # --- Handle API Success or Error from Response Body ---
132
+ if response.status_code == 200 and content_type_header.startswith(
133
+ "audio/"
134
+ ):
135
+ audio_bytes = response.content
136
+ if not audio_bytes:
137
+ message = (
138
+ "API returned success status but response body was empty."
139
+ )
140
+ logger.warning(
141
+ f"Venice Audio API (Voice: {voice_model}) returned 200 OK but empty audio content."
142
+ )
143
+ return {
144
+ "error": True,
145
+ "error_type": "NoContentError",
146
+ "message": message,
147
+ "status_code": response.status_code,
148
+ "voice_model": voice_model,
149
+ "requested_format": final_response_format,
150
+ }
151
+
152
+ # --- Store Audio ---
153
+ file_extension = final_response_format
154
+ audio_hash = hashlib.sha256(audio_bytes).hexdigest()
155
+ key = f"{self.category}/{voice_model}/{audio_hash}.{file_extension}"
156
+
157
+ size_limit = 1024 * 20 # 20Mb Size limit
158
+ stored_url = await store_file_bytes(
159
+ file_bytes=audio_bytes,
160
+ key=key,
161
+ file_type=FileType.AUDIO,
162
+ size_limit_bytes=size_limit,
163
+ )
164
+
165
+ if not stored_url:
166
+ message = "Failed to store audio: S3 storage is not configured."
167
+ logger.error(
168
+ f"Failed to store audio (Voice: {voice_model}): S3 storage is not configured."
169
+ )
170
+ return {
171
+ "error": True,
172
+ "error_type": "StorageConfigurationError",
173
+ "message": message,
174
+ "voice_model": voice_model,
175
+ "requested_format": final_response_format,
176
+ }
177
+
178
+ logger.info(
179
+ f"Venice TTS success: Voice='{voice_model}', Format='{final_response_format}', Stored='{stored_url}'"
180
+ )
181
+ # --- Return Success Dictionary ---
182
+ return {
183
+ "audio_url": stored_url,
184
+ "audio_bytes_sha256": audio_hash,
185
+ "content_type": content_type_header,
186
+ "voice_model": voice_model,
187
+ "tts_engine": tts_model_id,
188
+ "speed": speed if speed is not None else 1.0,
189
+ "response_format": final_response_format,
190
+ "input_text_length": len(input),
191
+ "error": False,
192
+ "status_code": response.status_code,
193
+ }
194
+ else:
195
+ # Non-200 API response or non-audio content
196
+ error_details: Any = f"Raw error response text: {response.text}"
197
+ try:
198
+ parsed_details = response.json()
199
+ error_details = parsed_details
200
+ except json.JSONDecodeError:
201
+ pass # Keep raw text if JSON parsing fails
202
+
203
+ message = "Venice Audio API returned a non-success status or unexpected content type."
204
+ logger.error(
205
+ f"Venice Audio API Error: Voice='{voice_model}', Format='{final_response_format}', Status={response.status_code}, Details: {error_details}"
206
+ )
207
+ return {
208
+ "error": True,
209
+ "error_type": "APIError",
210
+ "message": message,
211
+ "status_code": response.status_code,
212
+ "details": error_details,
213
+ "voice_model": voice_model,
214
+ "requested_format": final_response_format,
215
+ }
216
+
217
+ except Exception as e:
218
+ # Global exception handling for any uncaught error
219
+ error_type = type(
220
+ e
221
+ ).__name__ # Gets the class name of the exception (e.g., 'TimeoutException', 'ToolException')
222
+ message = f"An unexpected error occurred during audio generation for voice {voice_model}."
223
+ details = str(e) # The string representation of the exception
224
+
225
+ # Log the error with full traceback for debugging
226
+ logger.error(
227
+ f"Venice Audio Tool Global Error ({error_type}): {message} | Details: {details}",
228
+ exc_info=True,
229
+ )
230
+
231
+ return {
232
+ "error": True,
233
+ "error_type": error_type, # e.g., "TimeoutException", "ToolException", "ClientError", "ValueError"
234
+ "message": message,
235
+ "details": details,
236
+ "voice_model": voice_model,
237
+ "requested_format": final_response_format,
238
+ }
@@ -1,14 +1,11 @@
1
1
  import logging
2
2
  from typing import Any, Dict, Optional, Tuple
3
3
 
4
+ from langchain.tools.base import ToolException
4
5
  from pydantic import Field
5
6
 
6
7
  from intentkit.abstracts.skill import SkillStoreABC
7
- from intentkit.skills.base import (
8
- IntentKitSkill,
9
- SkillContext,
10
- ToolException,
11
- )
8
+ from intentkit.skills.base import IntentKitSkill
12
9
  from intentkit.skills.venice_image.api import (
13
10
  make_venice_api_request,
14
11
  )
@@ -47,7 +44,7 @@ class VeniceImageBaseTool(IntentKitSkill):
47
44
  description="The skill store for persisting data and configs."
48
45
  )
49
46
 
50
- def getSkillConfig(self, context: SkillContext) -> VeniceImageConfig:
47
+ def getSkillConfig(self, context) -> VeniceImageConfig:
51
48
  """
52
49
  Creates a VeniceImageConfig instance from a dictionary of configuration values.
53
50
 
@@ -58,19 +55,20 @@ class VeniceImageBaseTool(IntentKitSkill):
58
55
  A VeniceImageConfig object.
59
56
  """
60
57
 
58
+ skill_config = context.agent.skill_config(self.category)
61
59
  return VeniceImageConfig(
62
- api_key_provider=context.config.get("api_key_provider", "agent_owner"),
63
- safe_mode=context.config.get("safe_mode", True),
64
- hide_watermark=context.config.get("hide_watermark", True),
65
- embed_exif_metadata=context.config.get("embed_exif_metadata", False),
66
- negative_prompt=context.config.get(
60
+ api_key_provider=skill_config.get("api_key_provider", "agent_owner"),
61
+ safe_mode=skill_config.get("safe_mode", True),
62
+ hide_watermark=skill_config.get("hide_watermark", True),
63
+ embed_exif_metadata=skill_config.get("embed_exif_metadata", False),
64
+ negative_prompt=skill_config.get(
67
65
  "negative_prompt", "(worst quality: 1.4), bad quality, nsfw"
68
66
  ),
69
- rate_limit_number=context.config.get("rate_limit_number"),
70
- rate_limit_minutes=context.config.get("rate_limit_minutes"),
67
+ rate_limit_number=skill_config.get("rate_limit_number"),
68
+ rate_limit_minutes=skill_config.get("rate_limit_minutes"),
71
69
  )
72
70
 
73
- def get_api_key(self, context: SkillContext) -> str:
71
+ def get_api_key(self) -> str:
74
72
  """
75
73
  Retrieves the Venice AI API key based on the api_key_provider setting.
76
74
 
@@ -81,9 +79,11 @@ class VeniceImageBaseTool(IntentKitSkill):
81
79
  ToolException: If the API key is not found or provider is invalid.
82
80
  """
83
81
  try:
82
+ context = self.get_context()
84
83
  skillConfig = self.getSkillConfig(context=context)
85
84
  if skillConfig.api_key_provider == "agent_owner":
86
- agent_api_key = context.config.get("api_key")
85
+ skill_config = context.agent.skill_config(self.category)
86
+ agent_api_key = skill_config.get("api_key")
87
87
  if agent_api_key:
88
88
  logger.debug(
89
89
  f"Using agent-specific Venice API key for skill {self.name} in category {self.category}"
@@ -112,7 +112,7 @@ class VeniceImageBaseTool(IntentKitSkill):
112
112
  except Exception as e:
113
113
  raise ToolException(f"Failed to retrieve Venice API key: {str(e)}") from e
114
114
 
115
- async def apply_venice_rate_limit(self, context: SkillContext) -> None:
115
+ async def apply_venice_rate_limit(self, context) -> None:
116
116
  """
117
117
  Applies rate limiting to prevent exceeding the Venice AI API's rate limits.
118
118
 
@@ -121,7 +121,7 @@ class VeniceImageBaseTool(IntentKitSkill):
121
121
  - 'platform': uses system-wide configuration.
122
122
  """
123
123
  try:
124
- user_id = context.user_id
124
+ # Get user_id from the agent context (venice_image only supports agent_owner)
125
125
  skillConfig = self.getSkillConfig(context=context)
126
126
 
127
127
  if skillConfig.api_key_provider == "agent_owner":
@@ -129,6 +129,8 @@ class VeniceImageBaseTool(IntentKitSkill):
129
129
  limit_min = skillConfig.rate_limit_minutes
130
130
 
131
131
  if limit_num and limit_min:
132
+ # For agent_owner, use agent.id as user_id for rate limiting
133
+ user_id = context.agent.id
132
134
  logger.debug(
133
135
  f"Applying Agent rate limit ({limit_num}/{limit_min} min) for user {user_id} on {self.name}"
134
136
  )
@@ -145,6 +147,8 @@ class VeniceImageBaseTool(IntentKitSkill):
145
147
  )
146
148
 
147
149
  if system_limit_num and system_limit_min:
150
+ # For platform, use agent.id as user_id for rate limiting
151
+ user_id = context.agent.id
148
152
  logger.debug(
149
153
  f"Applying System rate limit ({system_limit_num}/{system_limit_min} min) for user {user_id} on {self.name}"
150
154
  )
@@ -158,7 +162,7 @@ class VeniceImageBaseTool(IntentKitSkill):
158
162
  raise ToolException(f"Failed to apply Venice rate limit: {str(e)}") from e
159
163
 
160
164
  async def post(
161
- self, path: str, payload: Dict[str, Any], context: SkillContext
165
+ self, path: str, payload: Dict[str, Any], context
162
166
  ) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]:
163
167
  """
164
168
  Makes a POST request to the Venice AI API using the `make_venice_api_request`
@@ -181,7 +185,7 @@ class VeniceImageBaseTool(IntentKitSkill):
181
185
  - If successful, success contains the JSON response from the API.
182
186
  - If an error occurs, success is an empty dictionary, and error contains error details.
183
187
  """
184
- api_key = self.get_api_key(context)
188
+ api_key = self.get_api_key()
185
189
 
186
190
  return await make_venice_api_request(
187
191
  api_key, path, payload, self.category, self.name