intentkit 0.6.0.dev13__py3-none-any.whl → 0.6.0.dev15__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 (70) hide show
  1. intentkit/__init__.py +1 -1
  2. intentkit/core/engine.py +2 -1
  3. intentkit/core/node.py +3 -1
  4. intentkit/models/agent_schema.json +8 -0
  5. intentkit/skills/base.py +37 -17
  6. intentkit/skills/cryptocompare/fetch_news.py +2 -2
  7. intentkit/skills/cryptocompare/fetch_price.py +2 -2
  8. intentkit/skills/cryptocompare/fetch_top_exchanges.py +2 -2
  9. intentkit/skills/cryptocompare/fetch_top_market_cap.py +2 -2
  10. intentkit/skills/cryptocompare/fetch_top_volume.py +2 -2
  11. intentkit/skills/cryptocompare/fetch_trading_signals.py +2 -2
  12. intentkit/skills/defillama/base.py +3 -3
  13. intentkit/skills/enso/base.py +2 -2
  14. intentkit/skills/enso/networks.py +1 -1
  15. intentkit/skills/enso/route.py +1 -1
  16. intentkit/skills/enso/tokens.py +1 -1
  17. intentkit/skills/firecrawl/clear.py +1 -1
  18. intentkit/skills/firecrawl/crawl.py +2 -10
  19. intentkit/skills/firecrawl/query.py +4 -4
  20. intentkit/skills/firecrawl/scrape.py +2 -8
  21. intentkit/skills/heurist/image_generation_animagine_xl.py +1 -1
  22. intentkit/skills/heurist/image_generation_arthemy_comics.py +1 -1
  23. intentkit/skills/heurist/image_generation_arthemy_real.py +1 -1
  24. intentkit/skills/heurist/image_generation_braindance.py +1 -1
  25. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +1 -1
  26. intentkit/skills/heurist/image_generation_flux_1_dev.py +1 -1
  27. intentkit/skills/heurist/image_generation_sdxl.py +1 -1
  28. intentkit/skills/http/README.md +78 -0
  29. intentkit/skills/http/__init__.py +100 -0
  30. intentkit/skills/http/base.py +21 -0
  31. intentkit/skills/http/get.py +96 -0
  32. intentkit/skills/http/http.svg +15 -0
  33. intentkit/skills/http/post.py +113 -0
  34. intentkit/skills/http/put.py +113 -0
  35. intentkit/skills/http/schema.json +92 -0
  36. intentkit/skills/lifi/token_execute.py +1 -1
  37. intentkit/skills/openai/dalle_image_generation.py +1 -1
  38. intentkit/skills/openai/gpt_image_generation.py +1 -1
  39. intentkit/skills/openai/gpt_image_to_image.py +1 -1
  40. intentkit/skills/supabase/__init__.py +116 -0
  41. intentkit/skills/supabase/base.py +72 -0
  42. intentkit/skills/supabase/delete_data.py +102 -0
  43. intentkit/skills/supabase/fetch_data.py +120 -0
  44. intentkit/skills/supabase/insert_data.py +70 -0
  45. intentkit/skills/supabase/invoke_function.py +74 -0
  46. intentkit/skills/supabase/schema.json +168 -0
  47. intentkit/skills/supabase/supabase.svg +15 -0
  48. intentkit/skills/supabase/update_data.py +105 -0
  49. intentkit/skills/supabase/upsert_data.py +77 -0
  50. intentkit/skills/system/read_agent_api_key.py +1 -1
  51. intentkit/skills/system/regenerate_agent_api_key.py +1 -1
  52. intentkit/skills/token/base.py +1 -39
  53. intentkit/skills/twitter/follow_user.py +3 -3
  54. intentkit/skills/twitter/get_mentions.py +6 -6
  55. intentkit/skills/twitter/get_timeline.py +5 -5
  56. intentkit/skills/twitter/get_user_by_username.py +3 -3
  57. intentkit/skills/twitter/get_user_tweets.py +5 -5
  58. intentkit/skills/twitter/like_tweet.py +3 -3
  59. intentkit/skills/twitter/post_tweet.py +4 -4
  60. intentkit/skills/twitter/reply_tweet.py +4 -4
  61. intentkit/skills/twitter/retweet.py +3 -3
  62. intentkit/skills/twitter/search_tweets.py +5 -5
  63. intentkit/skills/unrealspeech/text_to_speech.py +1 -1
  64. intentkit/skills/web_scraper/document_indexer.py +2 -2
  65. intentkit/skills/web_scraper/scrape_and_index.py +8 -8
  66. intentkit/skills/web_scraper/website_indexer.py +4 -4
  67. {intentkit-0.6.0.dev13.dist-info → intentkit-0.6.0.dev15.dist-info}/METADATA +1 -1
  68. {intentkit-0.6.0.dev13.dist-info → intentkit-0.6.0.dev15.dist-info}/RECORD +70 -52
  69. {intentkit-0.6.0.dev13.dist-info → intentkit-0.6.0.dev15.dist-info}/WHEEL +0 -0
  70. {intentkit-0.6.0.dev13.dist-info → intentkit-0.6.0.dev15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,21 @@
1
+ from typing import Type
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from intentkit.abstracts.skill import SkillStoreABC
6
+ from intentkit.skills.base import IntentKitSkill
7
+
8
+
9
+ class HttpBaseTool(IntentKitSkill):
10
+ """Base class for HTTP client tools."""
11
+
12
+ name: str = Field(description="The name of the tool")
13
+ description: str = Field(description="A description of what the tool does")
14
+ args_schema: Type[BaseModel]
15
+ skill_store: SkillStoreABC = Field(
16
+ description="The skill store for persisting data"
17
+ )
18
+
19
+ @property
20
+ def category(self) -> str:
21
+ return "http"
@@ -0,0 +1,96 @@
1
+ import logging
2
+ from typing import Any, Dict, Optional, Type
3
+
4
+ import httpx
5
+ from langchain_core.runnables import RunnableConfig
6
+ from pydantic import BaseModel, Field
7
+
8
+ from intentkit.skills.http.base import HttpBaseTool
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class HttpGetInput(BaseModel):
14
+ """Input for HTTP GET request."""
15
+
16
+ url: str = Field(description="The URL to send the GET request to")
17
+ headers: Optional[Dict[str, str]] = Field(
18
+ description="Optional headers to include in the request",
19
+ default=None,
20
+ )
21
+ params: Optional[Dict[str, Any]] = Field(
22
+ description="Optional query parameters to include in the request",
23
+ default=None,
24
+ )
25
+ timeout: Optional[float] = Field(
26
+ description="Request timeout in seconds (default: 30)",
27
+ default=30.0,
28
+ )
29
+
30
+
31
+ class HttpGet(HttpBaseTool):
32
+ """Tool for making HTTP GET requests.
33
+
34
+ This tool allows you to make HTTP GET requests to any URL with optional
35
+ headers and query parameters. It returns the response content as a string.
36
+
37
+ Attributes:
38
+ name: The name of the tool.
39
+ description: A description of what the tool does.
40
+ args_schema: The schema for the tool's input arguments.
41
+ """
42
+
43
+ name: str = "http_get"
44
+ description: str = (
45
+ "Make an HTTP GET request to a specified URL. "
46
+ "You can include custom headers and query parameters. "
47
+ "Returns the response content as text. "
48
+ "Use this when you need to fetch data from web APIs or websites."
49
+ )
50
+ args_schema: Type[BaseModel] = HttpGetInput
51
+
52
+ async def _arun(
53
+ self,
54
+ url: str,
55
+ headers: Optional[Dict[str, str]] = None,
56
+ params: Optional[Dict[str, Any]] = None,
57
+ timeout: float = 30.0,
58
+ config: RunnableConfig = None,
59
+ **kwargs,
60
+ ) -> str:
61
+ """Implementation of the HTTP GET request.
62
+
63
+ Args:
64
+ url: The URL to send the GET request to.
65
+ headers: Optional headers to include in the request.
66
+ params: Optional query parameters to include in the request.
67
+ timeout: Request timeout in seconds.
68
+ config: The runnable config (unused but required by interface).
69
+
70
+ Returns:
71
+ str: The response content as text, or error message if request fails.
72
+ """
73
+ try:
74
+ async with httpx.AsyncClient() as client:
75
+ response = await client.get(
76
+ url=url,
77
+ headers=headers,
78
+ params=params,
79
+ timeout=timeout,
80
+ )
81
+
82
+ # Raise an exception for bad status codes
83
+ response.raise_for_status()
84
+
85
+ # Return response content
86
+ return f"Status: {response.status_code}\nContent: {response.text}"
87
+
88
+ except httpx.TimeoutException:
89
+ return f"Error: Request to {url} timed out after {timeout} seconds"
90
+ except httpx.HTTPStatusError as e:
91
+ return f"Error: HTTP {e.response.status_code} - {e.response.text}"
92
+ except httpx.RequestError as e:
93
+ return f"Error: Failed to connect to {url} - {str(e)}"
94
+ except Exception as e:
95
+ logger.error(f"Unexpected error in HTTP GET request: {e}")
96
+ return f"Error: Unexpected error occurred - {str(e)}"
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
2
+ <!-- Background -->
3
+ <rect width="100" height="100" fill="#2563eb" rx="15"/>
4
+
5
+ <!-- HTTP text -->
6
+ <text x="50" y="35" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="white" text-anchor="middle">HTTP</text>
7
+
8
+ <!-- Arrow indicating request/response -->
9
+ <path d="M20 50 L35 45 L35 48 L65 48 L65 45 L80 50 L65 55 L65 52 L35 52 L35 55 Z" fill="white"/>
10
+
11
+ <!-- Globe/network icon -->
12
+ <circle cx="50" cy="75" r="12" fill="none" stroke="white" stroke-width="2"/>
13
+ <path d="M38 75 Q50 65 62 75 M38 75 Q50 85 62 75" fill="none" stroke="white" stroke-width="1.5"/>
14
+ <line x1="50" y1="63" x2="50" y2="87" stroke="white" stroke-width="1.5"/>
15
+ </svg>
@@ -0,0 +1,113 @@
1
+ import logging
2
+ from typing import Any, Dict, Optional, Type, Union
3
+
4
+ import httpx
5
+ from langchain_core.runnables import RunnableConfig
6
+ from pydantic import BaseModel, Field
7
+
8
+ from intentkit.skills.http.base import HttpBaseTool
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class HttpPostInput(BaseModel):
14
+ """Input for HTTP POST request."""
15
+
16
+ url: str = Field(description="The URL to send the POST request to")
17
+ data: Optional[Union[Dict[str, Any], str]] = Field(
18
+ description="The data to send in the request body. Can be a dictionary (will be sent as JSON) or a string",
19
+ default=None,
20
+ )
21
+ headers: Optional[Dict[str, str]] = Field(
22
+ description="Optional headers to include in the request",
23
+ default=None,
24
+ )
25
+ params: Optional[Dict[str, Any]] = Field(
26
+ description="Optional query parameters to include in the request",
27
+ default=None,
28
+ )
29
+ timeout: Optional[float] = Field(
30
+ description="Request timeout in seconds (default: 30)",
31
+ default=30.0,
32
+ )
33
+
34
+
35
+ class HttpPost(HttpBaseTool):
36
+ """Tool for making HTTP POST requests.
37
+
38
+ This tool allows you to make HTTP POST requests to any URL with optional
39
+ headers, query parameters, and request body data. It returns the response content as a string.
40
+
41
+ Attributes:
42
+ name: The name of the tool.
43
+ description: A description of what the tool does.
44
+ args_schema: The schema for the tool's input arguments.
45
+ """
46
+
47
+ name: str = "http_post"
48
+ description: str = (
49
+ "Make an HTTP POST request to a specified URL. "
50
+ "You can include custom headers, query parameters, and request body data. "
51
+ "Data can be provided as a dictionary (sent as JSON) or as a string. "
52
+ "Returns the response content as text. "
53
+ "Use this when you need to send data to web APIs or submit forms."
54
+ )
55
+ args_schema: Type[BaseModel] = HttpPostInput
56
+
57
+ async def _arun(
58
+ self,
59
+ url: str,
60
+ data: Optional[Union[Dict[str, Any], str]] = None,
61
+ headers: Optional[Dict[str, str]] = None,
62
+ params: Optional[Dict[str, Any]] = None,
63
+ timeout: float = 30.0,
64
+ config: RunnableConfig = None,
65
+ **kwargs,
66
+ ) -> str:
67
+ """Implementation of the HTTP POST request.
68
+
69
+ Args:
70
+ url: The URL to send the POST request to.
71
+ data: The data to send in the request body.
72
+ headers: Optional headers to include in the request.
73
+ params: Optional query parameters to include in the request.
74
+ timeout: Request timeout in seconds.
75
+ config: The runnable config (unused but required by interface).
76
+
77
+ Returns:
78
+ str: The response content as text, or error message if request fails.
79
+ """
80
+ try:
81
+ # Prepare headers
82
+ request_headers = headers or {}
83
+
84
+ # If data is a dictionary, send as JSON
85
+ if isinstance(data, dict):
86
+ if "content-type" not in {k.lower() for k in request_headers.keys()}:
87
+ request_headers["Content-Type"] = "application/json"
88
+
89
+ async with httpx.AsyncClient() as client:
90
+ response = await client.post(
91
+ url=url,
92
+ json=data if isinstance(data, dict) else None,
93
+ content=data if isinstance(data, str) else None,
94
+ headers=request_headers,
95
+ params=params,
96
+ timeout=timeout,
97
+ )
98
+
99
+ # Raise an exception for bad status codes
100
+ response.raise_for_status()
101
+
102
+ # Return response content
103
+ return f"Status: {response.status_code}\nContent: {response.text}"
104
+
105
+ except httpx.TimeoutException:
106
+ return f"Error: Request to {url} timed out after {timeout} seconds"
107
+ except httpx.HTTPStatusError as e:
108
+ return f"Error: HTTP {e.response.status_code} - {e.response.text}"
109
+ except httpx.RequestError as e:
110
+ return f"Error: Failed to connect to {url} - {str(e)}"
111
+ except Exception as e:
112
+ logger.error(f"Unexpected error in HTTP POST request: {e}")
113
+ return f"Error: Unexpected error occurred - {str(e)}"
@@ -0,0 +1,113 @@
1
+ import logging
2
+ from typing import Any, Dict, Optional, Type, Union
3
+
4
+ import httpx
5
+ from langchain_core.runnables import RunnableConfig
6
+ from pydantic import BaseModel, Field
7
+
8
+ from intentkit.skills.http.base import HttpBaseTool
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class HttpPutInput(BaseModel):
14
+ """Input for HTTP PUT request."""
15
+
16
+ url: str = Field(description="The URL to send the PUT request to")
17
+ data: Optional[Union[Dict[str, Any], str]] = Field(
18
+ description="The data to send in the request body. Can be a dictionary (will be sent as JSON) or a string",
19
+ default=None,
20
+ )
21
+ headers: Optional[Dict[str, str]] = Field(
22
+ description="Optional headers to include in the request",
23
+ default=None,
24
+ )
25
+ params: Optional[Dict[str, Any]] = Field(
26
+ description="Optional query parameters to include in the request",
27
+ default=None,
28
+ )
29
+ timeout: Optional[float] = Field(
30
+ description="Request timeout in seconds (default: 30)",
31
+ default=30.0,
32
+ )
33
+
34
+
35
+ class HttpPut(HttpBaseTool):
36
+ """Tool for making HTTP PUT requests.
37
+
38
+ This tool allows you to make HTTP PUT requests to any URL with optional
39
+ headers, query parameters, and request body data. It returns the response content as a string.
40
+
41
+ Attributes:
42
+ name: The name of the tool.
43
+ description: A description of what the tool does.
44
+ args_schema: The schema for the tool's input arguments.
45
+ """
46
+
47
+ name: str = "http_put"
48
+ description: str = (
49
+ "Make an HTTP PUT request to a specified URL. "
50
+ "You can include custom headers, query parameters, and request body data. "
51
+ "Data can be provided as a dictionary (sent as JSON) or as a string. "
52
+ "Returns the response content as text. "
53
+ "Use this when you need to update or replace data on web APIs."
54
+ )
55
+ args_schema: Type[BaseModel] = HttpPutInput
56
+
57
+ async def _arun(
58
+ self,
59
+ url: str,
60
+ data: Optional[Union[Dict[str, Any], str]] = None,
61
+ headers: Optional[Dict[str, str]] = None,
62
+ params: Optional[Dict[str, Any]] = None,
63
+ timeout: float = 30.0,
64
+ config: RunnableConfig = None,
65
+ **kwargs,
66
+ ) -> str:
67
+ """Implementation of the HTTP PUT request.
68
+
69
+ Args:
70
+ url: The URL to send the PUT request to.
71
+ data: The data to send in the request body.
72
+ headers: Optional headers to include in the request.
73
+ params: Optional query parameters to include in the request.
74
+ timeout: Request timeout in seconds.
75
+ config: The runnable config (unused but required by interface).
76
+
77
+ Returns:
78
+ str: The response content as text, or error message if request fails.
79
+ """
80
+ try:
81
+ # Prepare headers
82
+ request_headers = headers or {}
83
+
84
+ # If data is a dictionary, send as JSON
85
+ if isinstance(data, dict):
86
+ if "content-type" not in {k.lower() for k in request_headers.keys()}:
87
+ request_headers["Content-Type"] = "application/json"
88
+
89
+ async with httpx.AsyncClient() as client:
90
+ response = await client.put(
91
+ url=url,
92
+ json=data if isinstance(data, dict) else None,
93
+ content=data if isinstance(data, str) else None,
94
+ headers=request_headers,
95
+ params=params,
96
+ timeout=timeout,
97
+ )
98
+
99
+ # Raise an exception for bad status codes
100
+ response.raise_for_status()
101
+
102
+ # Return response content
103
+ return f"Status: {response.status_code}\nContent: {response.text}"
104
+
105
+ except httpx.TimeoutException:
106
+ return f"Error: Request to {url} timed out after {timeout} seconds"
107
+ except httpx.HTTPStatusError as e:
108
+ return f"Error: HTTP {e.response.status_code} - {e.response.text}"
109
+ except httpx.RequestError as e:
110
+ return f"Error: Failed to connect to {url} - {str(e)}"
111
+ except Exception as e:
112
+ logger.error(f"Unexpected error in HTTP PUT request: {e}")
113
+ return f"Error: Unexpected error occurred - {str(e)}"
@@ -0,0 +1,92 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "title": "HTTP Client",
5
+ "description": "HTTP client skills for making web requests",
6
+ "x-icon": "https://ai.service.crestal.dev/skills/http/http.jpg",
7
+ "x-tags": [
8
+ "HTTP",
9
+ "Web",
10
+ "API",
11
+ "Client"
12
+ ],
13
+ "properties": {
14
+ "enabled": {
15
+ "type": "boolean",
16
+ "title": "Enabled",
17
+ "description": "Whether this skill is enabled",
18
+ "default": true
19
+ },
20
+ "states": {
21
+ "type": "object",
22
+ "properties": {
23
+ "http_get": {
24
+ "type": "string",
25
+ "title": "HTTP GET",
26
+ "enum": [
27
+ "disabled",
28
+ "public",
29
+ "private"
30
+ ],
31
+ "x-enum-title": [
32
+ "Disabled",
33
+ "Agent Owner + All Users",
34
+ "Agent Owner Only"
35
+ ],
36
+ "description": "Make HTTP GET requests to fetch data from web APIs and websites",
37
+ "default": "private"
38
+ },
39
+ "http_post": {
40
+ "type": "string",
41
+ "title": "HTTP POST",
42
+ "enum": [
43
+ "disabled",
44
+ "public",
45
+ "private"
46
+ ],
47
+ "x-enum-title": [
48
+ "Disabled",
49
+ "Agent Owner + All Users",
50
+ "Agent Owner Only"
51
+ ],
52
+ "description": "Make HTTP POST requests to send data to web APIs and submit forms",
53
+ "default": "private"
54
+ },
55
+ "http_put": {
56
+ "type": "string",
57
+ "title": "HTTP PUT",
58
+ "enum": [
59
+ "disabled",
60
+ "public",
61
+ "private"
62
+ ],
63
+ "x-enum-title": [
64
+ "Disabled",
65
+ "Agent Owner + All Users",
66
+ "Agent Owner Only"
67
+ ],
68
+ "description": "Make HTTP PUT requests to update or replace data on web APIs",
69
+ "default": "private"
70
+ }
71
+ },
72
+ "description": "States for each HTTP client skill (disabled, public, or private)"
73
+ },
74
+ "api_key_provider": {
75
+ "type": "string",
76
+ "title": "API Key Provider",
77
+ "description": "Who provides the API key",
78
+ "enum": [
79
+ "platform"
80
+ ],
81
+ "x-enum-title": [
82
+ "Nation Hosted"
83
+ ],
84
+ "default": "platform"
85
+ }
86
+ },
87
+ "required": [
88
+ "states",
89
+ "enabled"
90
+ ],
91
+ "additionalProperties": true
92
+ }
@@ -129,7 +129,7 @@ class TokenExecute(LiFiBaseTool):
129
129
 
130
130
  # Get agent context for CDP wallet
131
131
  context = self.context_from_config(config)
132
- agent_id = context.agent.id
132
+ agent_id = context.agent_id
133
133
 
134
134
  self.logger.info(
135
135
  f"Executing LiFi transfer: {from_amount} {from_token} on {from_chain} -> {to_token} on {to_chain}"
@@ -109,7 +109,7 @@ class DALLEImageGeneration(OpenAIBaseTool):
109
109
  image_url = image_url.strip('"')
110
110
 
111
111
  # Generate a key with agent ID as prefix
112
- image_key = f"{context.agent.id}/dalle/{job_id}"
112
+ image_key = f"{context.agent_id}/dalle/{job_id}"
113
113
 
114
114
  # Store the image and get the CDN URL
115
115
  stored_url = await store_image(image_url, image_key)
@@ -133,7 +133,7 @@ class GPTImageGeneration(OpenAIBaseTool):
133
133
  image_bytes = base64.b64decode(base64_image)
134
134
 
135
135
  # Generate a key with agent ID as prefix
136
- image_key = f"{context.agent.id}/gpt-image/{job_id}"
136
+ image_key = f"{context.agent_id}/gpt-image/{job_id}"
137
137
 
138
138
  # Store the image bytes and get the CDN URL
139
139
  stored_url = await store_image_bytes(image_bytes, image_key, content_type)
@@ -157,7 +157,7 @@ class GPTImageToImage(OpenAIBaseTool):
157
157
  image_bytes = base64.b64decode(base64_image)
158
158
 
159
159
  # Generate a key with agent ID as prefix
160
- image_key = f"{context.agent.id}/gpt-image-edit/{job_id}"
160
+ image_key = f"{context.agent_id}/gpt-image-edit/{job_id}"
161
161
 
162
162
  # Store the image bytes and get the CDN URL
163
163
  stored_url = await store_image_bytes(image_bytes, image_key)
@@ -0,0 +1,116 @@
1
+ """Supabase skills."""
2
+
3
+ import logging
4
+ from typing import TypedDict
5
+
6
+ from intentkit.abstracts.skill import SkillStoreABC
7
+ from intentkit.skills.base import SkillConfig, SkillState
8
+ from intentkit.skills.supabase.base import SupabaseBaseTool
9
+ from intentkit.skills.supabase.delete_data import SupabaseDeleteData
10
+ from intentkit.skills.supabase.fetch_data import SupabaseFetchData
11
+ from intentkit.skills.supabase.insert_data import SupabaseInsertData
12
+ from intentkit.skills.supabase.invoke_function import SupabaseInvokeFunction
13
+ from intentkit.skills.supabase.update_data import SupabaseUpdateData
14
+ from intentkit.skills.supabase.upsert_data import SupabaseUpsertData
15
+
16
+ # Cache skills at the system level, because they are stateless
17
+ _cache: dict[str, SupabaseBaseTool] = {}
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class SkillStates(TypedDict):
23
+ fetch_data: SkillState
24
+ insert_data: SkillState
25
+ update_data: SkillState
26
+ upsert_data: SkillState
27
+ delete_data: SkillState
28
+ invoke_function: SkillState
29
+
30
+
31
+ class Config(SkillConfig):
32
+ """Configuration for Supabase skills."""
33
+
34
+ states: SkillStates
35
+ supabase_url: str
36
+ supabase_key: str
37
+ public_write_tables: str = ""
38
+
39
+
40
+ async def get_skills(
41
+ config: "Config",
42
+ is_private: bool,
43
+ store: SkillStoreABC,
44
+ **_,
45
+ ) -> list[SupabaseBaseTool]:
46
+ """Get all Supabase skills."""
47
+ available_skills = []
48
+
49
+ # Include skills based on their state
50
+ for skill_name, state in config["states"].items():
51
+ if state == "disabled":
52
+ continue
53
+ elif state == "public" or (state == "private" and is_private):
54
+ available_skills.append(skill_name)
55
+
56
+ # Get each skill using the cached getter
57
+ result = []
58
+ for name in available_skills:
59
+ skill = get_supabase_skill(name, store)
60
+ if skill:
61
+ result.append(skill)
62
+ return result
63
+
64
+
65
+ def get_supabase_skill(
66
+ name: str,
67
+ store: SkillStoreABC,
68
+ ) -> SupabaseBaseTool:
69
+ """Get a Supabase skill by name.
70
+
71
+ Args:
72
+ name: The name of the skill to get
73
+ store: The skill store for persisting data
74
+
75
+ Returns:
76
+ The requested Supabase skill
77
+ """
78
+ if name == "fetch_data":
79
+ if name not in _cache:
80
+ _cache[name] = SupabaseFetchData(
81
+ skill_store=store,
82
+ )
83
+ return _cache[name]
84
+ elif name == "insert_data":
85
+ if name not in _cache:
86
+ _cache[name] = SupabaseInsertData(
87
+ skill_store=store,
88
+ )
89
+ return _cache[name]
90
+ elif name == "update_data":
91
+ if name not in _cache:
92
+ _cache[name] = SupabaseUpdateData(
93
+ skill_store=store,
94
+ )
95
+ return _cache[name]
96
+ elif name == "upsert_data":
97
+ if name not in _cache:
98
+ _cache[name] = SupabaseUpsertData(
99
+ skill_store=store,
100
+ )
101
+ return _cache[name]
102
+ elif name == "delete_data":
103
+ if name not in _cache:
104
+ _cache[name] = SupabaseDeleteData(
105
+ skill_store=store,
106
+ )
107
+ return _cache[name]
108
+ elif name == "invoke_function":
109
+ if name not in _cache:
110
+ _cache[name] = SupabaseInvokeFunction(
111
+ skill_store=store,
112
+ )
113
+ return _cache[name]
114
+ else:
115
+ logger.warning(f"Unknown Supabase skill: {name}")
116
+ return None
@@ -0,0 +1,72 @@
1
+ from typing import Type
2
+
3
+ from langchain_core.tools import ToolException
4
+ from pydantic import BaseModel, Field
5
+
6
+ from intentkit.abstracts.skill import SkillStoreABC
7
+ from intentkit.skills.base import IntentKitSkill, SkillContext
8
+
9
+
10
+ class SupabaseBaseTool(IntentKitSkill):
11
+ """Base class for Supabase tools."""
12
+
13
+ name: str = Field(description="The name of the tool")
14
+ description: str = Field(description="A description of what the tool does")
15
+ args_schema: Type[BaseModel]
16
+ skill_store: SkillStoreABC = Field(
17
+ description="The skill store for persisting data"
18
+ )
19
+
20
+ @property
21
+ def category(self) -> str:
22
+ return "supabase"
23
+
24
+ def get_supabase_config(self, config: dict) -> tuple[str, str]:
25
+ """Get Supabase URL and key from config.
26
+
27
+ Args:
28
+ config: The agent configuration
29
+
30
+ Returns:
31
+ Tuple of (supabase_url, supabase_key)
32
+
33
+ Raises:
34
+ ValueError: If required config is missing
35
+ """
36
+ supabase_url = config.get("supabase_url")
37
+ supabase_key = config.get("supabase_key")
38
+
39
+ if not supabase_url:
40
+ raise ValueError("supabase_url is required in config")
41
+ if not supabase_key:
42
+ raise ValueError("supabase_key is required in config")
43
+
44
+ return supabase_url, supabase_key
45
+
46
+ def validate_table_access(self, table: str, context: SkillContext) -> None:
47
+ """Validate if the table can be accessed for write operations in public mode.
48
+
49
+ Args:
50
+ table: The table name to validate
51
+ context: The skill context containing configuration and mode info
52
+
53
+ Raises:
54
+ ToolException: If table access is not allowed in public mode
55
+ """
56
+ # If in private mode (owner mode), no restrictions apply
57
+ if context.is_private:
58
+ return
59
+
60
+ # In public mode, check if table is in allowed list
61
+ public_write_tables = context.config.get("public_write_tables", "")
62
+ if not public_write_tables:
63
+ return
64
+
65
+ allowed_tables = [
66
+ t.strip() for t in public_write_tables.split(",") if t.strip()
67
+ ]
68
+ if table not in allowed_tables:
69
+ raise ToolException(
70
+ f"Table '{table}' is not allowed for public write operations. "
71
+ f"Allowed tables: {', '.join(allowed_tables)}"
72
+ )