mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 21:16:46 +00:00
* Implement Mika runtime sync and go-live controls * Add CI validation workflow * Align CI with validated runtime checks * Fix Mika CI install workflow
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
|
import { corsHeaders } from "../_shared/cors.ts";
|
|
import {
|
|
DEFAULT_OLLAMA_MODEL,
|
|
normalizeOllamaModelSelection,
|
|
} from "../_shared/hermes-config.ts";
|
|
|
|
const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!;
|
|
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
|
const OLLAMA_API_KEY = Deno.env.get("OLLAMA_API_KEY");
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
interface OllamaTagModel {
|
|
name?: string;
|
|
model?: string;
|
|
modified_at?: string;
|
|
size?: number;
|
|
digest?: string;
|
|
details?: Record<string, unknown>;
|
|
}
|
|
|
|
Deno.serve(async (req) => {
|
|
if (req.method === "OPTIONS") {
|
|
return new Response(null, { headers: corsHeaders });
|
|
}
|
|
|
|
try {
|
|
const authHeader = req.headers.get("Authorization") ?? "";
|
|
const jwt = authHeader.replace(/^Bearer\s+/i, "");
|
|
if (!jwt) return jsonResponse({ error: "missing authorization" }, 401);
|
|
|
|
const admin = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
|
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
});
|
|
|
|
const { data: userData, error: userErr } = await admin.auth.getUser(jwt);
|
|
if (userErr || !userData?.user) {
|
|
return jsonResponse({ error: "invalid token" }, 401);
|
|
}
|
|
|
|
const { data: isAdmin, error: roleErr } = await admin.rpc("has_role", {
|
|
_user_id: userData.user.id,
|
|
_role: "admin",
|
|
});
|
|
if (roleErr || !isAdmin) {
|
|
return jsonResponse({ error: "admin role required" }, 403);
|
|
}
|
|
|
|
if (!OLLAMA_API_KEY) {
|
|
return jsonResponse({ error: "OLLAMA_API_KEY not configured" }, 500);
|
|
}
|
|
|
|
const res = await fetch("https://ollama.com/api/tags", {
|
|
headers: {
|
|
Authorization: `Bearer ${OLLAMA_API_KEY}`,
|
|
},
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
return jsonResponse(
|
|
{
|
|
error: `ollama tags request failed: ${res.status}`,
|
|
detail: text,
|
|
},
|
|
502,
|
|
);
|
|
}
|
|
|
|
const payload = await res.json().catch(() => ({}));
|
|
const models = Array.isArray(payload?.models) ? (payload.models as OllamaTagModel[]) : [];
|
|
|
|
const normalized = models
|
|
.map((item) => {
|
|
const raw = item.name || item.model || "";
|
|
const name = normalizeOllamaModelSelection(raw);
|
|
return {
|
|
name,
|
|
raw_name: raw,
|
|
modified_at: item.modified_at ?? null,
|
|
size: item.size ?? null,
|
|
digest: item.digest ?? null,
|
|
details: item.details ?? {},
|
|
};
|
|
})
|
|
.filter((item) => /(?:-cloud|:cloud)$/.test(item.name))
|
|
.sort((a, b) => {
|
|
if (a.name === DEFAULT_OLLAMA_MODEL) return -1;
|
|
if (b.name === DEFAULT_OLLAMA_MODEL) return 1;
|
|
return a.name.localeCompare(b.name);
|
|
});
|
|
|
|
return jsonResponse({
|
|
models: normalized,
|
|
default_model: DEFAULT_OLLAMA_MODEL,
|
|
source_endpoint: "https://ollama.com/api/tags",
|
|
});
|
|
} catch (err) {
|
|
return jsonResponse(
|
|
{ error: err instanceof Error ? err.message : "unexpected error" },
|
|
500,
|
|
);
|
|
}
|
|
});
|