"""Functions Handler - manages Supabase Edge Functions"""
import json
from typing import Any
from plugins.supabase.client import SupabaseClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (8 tools)"""
return [
{
"name": "invoke_function",
"method_name": "invoke_function",
"description": "Invoke a Supabase Edge Function with POST method. Pass data in the body parameter.",
"schema": {
"type": "object",
"properties": {
"function_name": {
"type": "string",
"description": "Name of the Edge Function to invoke",
},
"body": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "JSON body to send to the function",
},
"headers": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Additional headers to send",
},
},
"required": ["function_name"],
},
"scope": "write",
},
{
"name": "invoke_function_get",
"method_name": "invoke_function_get",
"description": "Invoke a Supabase Edge Function with GET method. Use for read-only operations.",
"schema": {
"type": "object",
"properties": {
"function_name": {
"type": "string",
"description": "Name of the Edge Function to invoke",
},
"params": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Query parameters to send",
},
},
"required": ["function_name"],
},
"scope": "read",
},
{
"name": "list_edge_functions",
"method_name": "list_edge_functions",
"description": "List all deployed Edge Functions. Note: This reads from the functions volume in Self-Hosted.",
"schema": {"type": "object", "properties": {}},
"scope": "read",
},
{
"name": "get_function_info",
"method_name": "get_function_info",
"description": "Get information about a specific Edge Function including its configuration.",
"schema": {
"type": "object",
"properties": {
"function_name": {"type": "string", "description": "Name of the function"}
},
"required": ["function_name"],
},
"scope": "read",
},
{
"name": "test_function",
"method_name": "test_function",
"description": "Test an Edge Function with sample data and return the response.",
"schema": {
"type": "object",
"properties": {
"function_name": {
"type": "string",
"description": "Name of the function to test",
},
"test_data": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Test data to send",
},
"method": {
"type": "string",
"enum": ["GET", "POST"],
"description": "HTTP method",
"default": "POST",
},
},
"required": ["function_name"],
},
"scope": "write",
},
{
"name": "get_function_url",
"method_name": "get_function_url",
"description": "Get the full URL for invoking an Edge Function.",
"schema": {
"type": "object",
"properties": {
"function_name": {"type": "string", "description": "Name of the function"}
},
"required": ["function_name"],
},
"scope": "read",
},
{
"name": "check_function_health",
"method_name": "check_function_health",
"description": "Check if an Edge Function is responding correctly.",
"schema": {
"type": "object",
"properties": {
"function_name": {"type": "string", "description": "Name of the function"}
},
"required": ["function_name"],
},
"scope": "read",
},
{
"name": "invoke_function_batch",
"method_name": "invoke_function_batch",
"description": "Invoke an Edge Function multiple times with different payloads. Useful for batch processing.",
"schema": {
"type": "object",
"properties": {
"function_name": {"type": "string", "description": "Name of the function"},
"payloads": {
"type": "array",
"items": {"type": "object"},
"description": "Array of payloads to send",
},
},
"required": ["function_name", "payloads"],
},
"scope": "write",
},
]
# =====================
# Functions Operations (8 tools)
# =====================
async def invoke_function(
client: SupabaseClient,
function_name: str,
body: dict | None = None,
headers: dict | None = None,
) -> str:
"""Invoke an Edge Function with POST"""
try:
result = await client.invoke_function(function_name=function_name, body=body, method="POST")
return json.dumps(
{"success": True, "function": function_name, "method": "POST", "response": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def invoke_function_get(
client: SupabaseClient, function_name: str, params: dict | None = None
) -> str:
"""Invoke an Edge Function with GET"""
try:
# Build query string
endpoint = f"/functions/v1/{function_name}"
if params:
query = "&".join(f"{k}={v}" for k, v in params.items())
endpoint = f"{endpoint}?{query}"
result = await client.request("GET", endpoint, use_service_role=False)
return json.dumps(
{
"success": True,
"function": function_name,
"method": "GET",
"params": params,
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def list_edge_functions(client: SupabaseClient) -> str:
"""List deployed Edge Functions"""
try:
# In self-hosted, we can try to list functions via the API
# or check the functions volume. We'll try a health-check approach.
functions_info = {
"note": "In Self-Hosted Supabase, Edge Functions are deployed in volumes/functions/",
"endpoint": f"{client.base_url}/functions/v1/",
"available": True,
}
# Try to verify the functions endpoint is available
try:
await client.request("GET", "/functions/v1/", use_service_role=False)
functions_info["status"] = "Functions endpoint available"
except Exception as e:
functions_info["status"] = f"Functions endpoint check: {str(e)}"
return json.dumps(
{
"success": True,
"functions_info": functions_info,
"tip": "Deploy functions by adding them to volumes/functions/ directory",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def get_function_info(client: SupabaseClient, function_name: str) -> str:
"""Get function information"""
try:
info = {
"name": function_name,
"url": f"{client.base_url}/functions/v1/{function_name}",
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"auth_required": True,
"cors_enabled": True,
}
# Try to check if function exists by making a request
try:
await client.request(
"OPTIONS", f"/functions/v1/{function_name}", use_service_role=False
)
info["status"] = "available"
except Exception as e:
if "404" in str(e):
info["status"] = "not_found"
else:
info["status"] = "unknown"
info["check_error"] = str(e)
return json.dumps({"success": True, "function_info": info}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def test_function(
client: SupabaseClient, function_name: str, test_data: dict | None = None, method: str = "POST"
) -> str:
"""Test an Edge Function"""
try:
if method.upper() == "GET":
result = await client.request(
"GET", f"/functions/v1/{function_name}", params=test_data, use_service_role=False
)
else:
result = await client.invoke_function(
function_name=function_name, body=test_data, method="POST"
)
return json.dumps(
{
"success": True,
"function": function_name,
"method": method.upper(),
"test_data": test_data,
"response": result,
"status": "Function responded successfully",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps(
{
"success": False,
"function": function_name,
"method": method.upper(),
"test_data": test_data,
"error": str(e),
"status": "Function test failed",
},
indent=2,
ensure_ascii=False,
)
async def get_function_url(client: SupabaseClient, function_name: str) -> str:
"""Get the full URL for a function"""
try:
url = f"{client.base_url}/functions/v1/{function_name}"
return json.dumps(
{
"success": True,
"function": function_name,
"url": url,
"curl_example": f"curl -X POST '{url}' -H 'Authorization: Bearer <anon_key>' -H 'Content-Type: application/json' -d '{{}}'",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def check_function_health(client: SupabaseClient, function_name: str) -> str:
"""Check if a function is healthy"""
try:
healthy = False
response_time = None
error = None
import time
start = time.time()
try:
# Try to invoke with empty body
await client.invoke_function(function_name=function_name, body={}, method="POST")
healthy = True
except Exception as e:
error = str(e)
# 400 errors might mean the function is running but needs valid input
if "400" in str(e) or "422" in str(e):
healthy = True
error = "Function running (returned validation error)"
response_time = round((time.time() - start) * 1000, 2)
return json.dumps(
{
"success": True,
"function": function_name,
"healthy": healthy,
"response_time_ms": response_time,
"error": error,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def invoke_function_batch(
client: SupabaseClient, function_name: str, payloads: list[dict]
) -> str:
"""Invoke a function with multiple payloads"""
try:
results = []
success_count = 0
error_count = 0
for i, payload in enumerate(payloads):
try:
result = await client.invoke_function(
function_name=function_name, body=payload, method="POST"
)
results.append({"index": i, "success": True, "response": result})
success_count += 1
except Exception as e:
results.append({"index": i, "success": False, "error": str(e)})
error_count += 1
return json.dumps(
{
"success": True,
"function": function_name,
"total": len(payloads),
"succeeded": success_count,
"failed": error_count,
"results": results,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)