import { AI_PROVIDER } from "@/lib/env"; /** Optional OpenAI embeddings. Model availability is verified at runtime (GET /v1/models/) before first use; failures degrade to lexical only. */ const MODEL = process.env.OPENAI_EMBEDDING_MODEL ?? "text-embedding-3-small"; export const EMBEDDING_DIM = 1536; let verified: boolean | null = null; export function embeddingsEnabled() { return AI_PROVIDER === "openai" && !!process.env.OPENAI_API_KEY; } export async function verifyEmbeddingModel(): Promise<{ ok: boolean; model: string; reason?: string }> { if (!embeddingsEnabled()) return { ok: false, model: MODEL, reason: "AI provider disabled" }; if (verified !== null) return { ok: verified, model: MODEL }; try { const r = await fetch(`https://api.openai.com/v1/models/${encodeURIComponent(MODEL)}`, { headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, signal: AbortSignal.timeout(15000) }); verified = r.ok; return { ok: r.ok, model: MODEL, reason: r.ok ? undefined : `HTTP ${r.status}` }; } catch (e) { verified = false; return { ok: false, model: MODEL, reason: (e as Error).message }; } } /** Embed up to 64 inputs per call; inputs are truncated to ~8,000 characters (model limit 8,191 tokens). Returns null when disabled. */ export async function embed(inputs: string[]): Promise { if (!embeddingsEnabled()) return null; const v = await verifyEmbeddingModel(); if (!v.ok) return null; const out: number[][] = []; for (let i = 0; i < inputs.length; i += 64) { const batch = inputs.slice(i, i + 64).map((s) => s.slice(0, 8000)); const r = await fetch("https://api.openai.com/v1/embeddings", { method: "POST", headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ model: MODEL, input: batch, dimensions: EMBEDDING_DIM }), signal: AbortSignal.timeout(60000) }); if (!r.ok) throw new Error(`embeddings HTTP ${r.status}: ${(await r.text()).slice(0, 200)}`); const j = (await r.json()) as { data: { index: number; embedding: number[] }[] }; for (const d of j.data.sort((a, b) => a.index - b.index)) out.push(d.embedding); } return out; } export const EMBEDDING_MODEL = MODEL;