Bramka AI wbudowana w Twoje Edge Functions.
Nie instalujesz dodatkowego pakietu. Weryfikujesz JWT Supabase i przekazujesz identyfikator użytkownika w jednym nagłówku, a każde wywołanie jest mierzone, rozliczane i rejestrowane dla tej osoby — bez tworzenia własnej warstwy pomiarowej.
const BASE = "https://api.odnoga.com/v1";
import { createClient } from "jsr:@supabase/supabase-js@2";
export async function odnoga(req: Request) {
const jwt = (req.headers.get("authorization") ?? "").split(" ")[1];
// Verify the token — a decoded-but-unverified subject can be forged, and the
// spend would land on someone else's account.
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
);
const endUser = jwt ? (await supabase.auth.getUser(jwt)).data.user?.id : undefined;
return (path: string, body: unknown) =>
fetch(BASE + path, {
method: "POST",
headers: {
"content-type": "application/json",
Authorization: `Bearer ${Deno.env.get("ODNOGA_API_KEY")}`,
...(endUser ? { "x-odnoga-end-user": endUser } : {}),
},
body: JSON.stringify(body),
});
}
// supabase/functions/chat/index.ts
Deno.serve(async (req) => {
const ai = await odnoga(req);
const r = await ai("/chat/completions", {
model: "auto",
messages: [{ role: "user", content: "Hi" }],
});
return new Response(r.body, { headers: { "content-type": "application/json" } });
});
Jeden sekret: supabase secrets set ODNOGA_API_KEY=sk_live_xxx
Dla środowiska Supabase
Helper do skopiowania
Jeden mały plik w _shared/. Bez pakietu, dodatkowego etapu budowania ani wersji do pilnowania.
end_user z JWT
Odczytujesz auth.uid() z nagłówka Authorization i wysyłasz jako x-odnoga-end-user. Każde wywołanie jest mierzone dla konkretnego użytkownika.
RAG nad pgvector
Embeddingi i czat w dwóch wywołaniach; supabase.rpc("match_documents") pomiędzy. Bez kleju.
Traceability dla edge functions
Każde wywołanie ma własny zapis: organizacja, użytkownik końcowy, wersja promptu, model, koszt i czas odpowiedzi.
Jeden sekret do ustawienia
supabase secrets set ODNOGA_API_KEY=… i rozwiązanie działa.
Streaming, obrazki, embeddingi
Te same OpenAI-compatible endpointy dla każdej modalności — /chat/completions, /embeddings, /images/generations.
RAG z pgvector — od początku do końca
const ai = await odnoga(req);
const { data: emb } = await ai("/embeddings", {
model: "text-embedding-3-small",
input: question,
}).then((r) => r.json());
const { data: docs } = await supabase.rpc("match_documents", {
query_embedding: emb[0].embedding,
match_count: 5,
});
const r = await ai("/chat/completions", {
model: "gpt-4o-mini",
messages: [
{ role: "system", content: `Use:\n${docs.map(d => d.content).join("\n---\n")}` },
{ role: "user", content: question },
],
});Najczęstsze pytania
Czy to oficjalny projekt Supabase?
Nie — jesteśmy niezależną bramką. Integracja to zwykłe wywołanie HTTP z Twojej Edge Function, więc nic w Twoim projekcie nie zależy od tego, czy wydamy pakiet.
Czy jest pakiet npm lub JSR?
Publikowany SDK jest w przygotowaniu. Dziś integracja jest celowo bez pakietu: endpoint HTTP zgodny z OpenAI oraz jeden nagłówek. Każde SDK OpenAI działa po ustawieniu base_url na odnogę.
A co z row-level security?
odnoga nigdy nie dotyka Twoich tabel. Zapisuje identyfikator użytkownika końcowego, który wysyłasz, aby przypisać koszt. Twój klient Supabase i RLS nadal określają, jakie dane widzi użytkownik.
Czy mogę z przeglądarki (tokeny eut)?
Tak. Generujesz krótkotrwały token eut_ z edge function przez POST /v1/sessions, przekazujesz do przeglądarki i używasz jako bearer.