Local Setup
Edge Functions
Edge Functions
Edge Functions are serverless functions that run on Supabase's global edge network. HIVE Protocol uses them for AI integrations, webhook processing, and secure operations.
Overview
┌─────────────────────────────────────────────────────────────────┐
│ Edge Function Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Client Request │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Supabase Edge Runtime (Deno) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ agent- │ │ execute │ │ test- │ │ webhook │ │ │
│ │ │ respond │ │ -tool │ │ webhook │ │dispatch │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ OpenAI │ │External │ │ User │ │External │ │
│ │Anthropic│ │ APIs │ │Endpoint │ │Services │ │
│ │ Google │ │ │ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘HIVE Protocol Edge Functions
| Function | Purpose | Auth Required |
|---|---|---|
| agent-respond | Generate AI agent responses | Yes |
| execute-tool | Run tool integrations | Yes |
| generate-tool | AI-generate tool schemas | Yes |
| test-webhook | Send test webhook events | Yes |
| webhook-dispatcher | Process webhook delivery queue | Yes |
| check-rate-limit | Validate rate limits | Yes |
| two-factor-auth | Handle 2FA operations | Yes |
| delete-account | Securely delete user data | Yes |
| admin-stats | Admin dashboard statistics | Yes (Admin) |
| admin-users | Admin user management | Yes (Admin) |
Function Structure
Each edge function follows this structure:
supabase/functions/
├── _shared/ # Shared code
│ └── sanitize.ts # Input sanitization
├── agent-respond/
│ └── index.ts # Main function file
├── execute-tool/
│ └── index.ts
├── test-webhook/
│ └── index.ts
└── ... (other functions)Writing Edge Functions
Basic Template
// supabase/functions/my-function/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Client-Info, Apikey",
};
Deno.serve(async (req: Request) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return new Response(null, { status: 200, headers: corsHeaders });
}
try {
// Verify authentication
const authHeader = req.headers.get("Authorization");
if (!authHeader) {
return new Response(
JSON.stringify({ error: "Missing authorization" }),
{ status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// Parse request body
const body = await req.json();
// Your function logic here
const result = { success: true, data: body };
return new Response(
JSON.stringify(result),
{
status: 200,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
} catch (error) {
console.error("Function error:", error);
return new Response(
JSON.stringify({ error: error.message }),
{
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
});With Supabase Client
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "npm:@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Client-Info, Apikey",
};
Deno.serve(async (req: Request) => {
if (req.method === "OPTIONS") {
return new Response(null, { status: 200, headers: corsHeaders });
}
try {
// Create Supabase client with user's auth
const supabaseClient = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
{
global: {
headers: { Authorization: req.headers.get("Authorization")! },
},
}
);
// Get authenticated user
const { data: { user }, error: authError } = await supabaseClient.auth.getUser();
if (authError || !user) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// Query database as user
const { data: agents, error } = await supabaseClient
.from("agents")
.select("*")
.eq("user_id", user.id);
return new Response(
JSON.stringify({ agents }),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});With External AI APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Client-Info, Apikey",
};
Deno.serve(async (req: Request) => {
if (req.method === "OPTIONS") {
return new Response(null, { status: 200, headers: corsHeaders });
}
try {
const { message, model = "gpt-4o" } = await req.json();
// Call OpenAI API
const openaiResponse = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${Deno.env.get("OPENAI_API_KEY")}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model,
messages: [{ role: "user", content: message }],
max_tokens: 4096,
}),
});
const data = await openaiResponse.json();
return new Response(
JSON.stringify({ response: data.choices[0].message.content }),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});Deploying Functions
Using Supabase CLI
# Deploy a single function
supabase functions deploy agent-respond
# Deploy all functions
supabase functions deploy
# Deploy with specific project
supabase functions deploy --project-ref your-project-refUsing Dashboard
- Navigate to Edge Functions in Supabase dashboard
- Click "Deploy a new function"
- Upload your function code
- Configure settings
Environment Variables
Edge functions have access to these built-in variables:
Deno.env.get("SUPABASE_URL") // Your project URL
Deno.env.get("SUPABASE_ANON_KEY") // Public anon key
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") // Service role key
Deno.env.get("SUPABASE_DB_URL") // Direct database URLSetting Custom Secrets
# Set a secret
supabase secrets set OPENAI_API_KEY=sk-...
# Set multiple secrets
supabase secrets set \
OPENAI_API_KEY=sk-... \
ANTHROPIC_API_KEY=sk-ant-...
# List secrets
supabase secrets list
# Unset a secret
supabase secrets unset OPENAI_API_KEYAccess in function:
const openaiKey = Deno.env.get("OPENAI_API_KEY");Calling Edge Functions
From Client (Browser)
const response = await fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/agent-respond`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${session.access_token}`,
"Content-Type": "application/json",
"apikey": process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
},
body: JSON.stringify({
swarm_id: "uuid",
message: "Hello!",
}),
}
);
const data = await response.json();Using Supabase Client
const { data, error } = await supabase.functions.invoke("agent-respond", {
body: {
swarm_id: "uuid",
message: "Hello!",
},
});With Streaming
const response = await fetch(url, {
method: "POST",
headers: { ... },
body: JSON.stringify({ stream: true, ... }),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (reader) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// Process streaming data
console.log(chunk);
}Function Patterns
Rate Limiting
const rateLimitKey = `rate_limit:${user.id}`;
const { data: count } = await supabaseAdmin
.from("rate_limits")
.select("count")
.eq("key", rateLimitKey)
.single();
if (count && count.count >= 100) {
return new Response(
JSON.stringify({ error: "Rate limit exceeded" }),
{ status: 429, headers: corsHeaders }
);
}Error Handling
try {
const result = await riskyOperation();
return new Response(JSON.stringify(result), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
} catch (error) {
// Log error for debugging
console.error("Function error:", {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
});
// Return user-friendly error
const status = error.status || 500;
const message = status === 500 ? "Internal server error" : error.message;
return new Response(
JSON.stringify({ error: message, code: error.code }),
{ status, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}Input Validation
interface RequestBody {
swarm_id: string;
message: string;
agent_id?: string;
}
function validateRequest(body: unknown): body is RequestBody {
if (!body || typeof body !== "object") return false;
const b = body as Record<string, unknown>;
return (
typeof b.swarm_id === "string" &&
typeof b.message === "string" &&
(b.agent_id === undefined || typeof b.agent_id === "string")
);
}
Deno.serve(async (req) => {
const body = await req.json();
if (!validateRequest(body)) {
return new Response(
JSON.stringify({ error: "Invalid request body" }),
{ status: 400, headers: corsHeaders }
);
}
// body is now typed as RequestBody
const { swarm_id, message } = body;
});Monitoring & Logs
View Function Logs
# Stream logs
supabase functions logs agent-respond --follow
# View recent logs
supabase functions logs agent-respond --limit 100In Dashboard
- Navigate to Edge Functions
- Select a function
- Click "Logs" tab
- Filter by time range or search
Adding Custom Logs
console.log("Info:", { action: "process_message", swarm_id });
console.warn("Warning:", { rate_limit_approaching: true });
console.error("Error:", { message: error.message, stack: error.stack });Testing Functions
Local Testing
# Start local Supabase
supabase start
# Serve functions locally
supabase functions serve
# In another terminal, test
curl -X POST http://localhost:54321/functions/v1/agent-respond \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"swarm_id": "test", "message": "Hello"}'Unit Testing
// agent-respond.test.ts
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
Deno.test("validates input", async () => {
const response = await fetch("http://localhost:54321/functions/v1/agent-respond", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
});
assertEquals(response.status, 400);
});Troubleshooting
Function Not Found
# Verify function is deployed
supabase functions list
# Redeploy
supabase functions deploy agent-respondCORS Errors
Ensure CORS headers are set for all responses including errors:
// Always include corsHeaders in responses
return new Response(data, { headers: { ...corsHeaders, ... } });Timeout Issues
// Functions have 150s timeout by default
// For long operations, use streaming or background tasks
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
try {
const response = await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timeout);
}Next Steps
- [Troubleshooting](/docs/local-setup/troubleshooting): Common issues
- [Webhooks API](/docs/api/webhooks-api): Webhook functions
- [Tools API](/docs/api/tools-api): Tool execution