import { ENV } from "./_core/env"; export type AiEngineStatus = { state: "available" | "unconfigured" | "unreachable"; engine?: { name: string; provider: string; slug: string; model?: string; accessMode?: "gateway" | "recommendation_only"; configured: boolean; origin: "proposal" | "manual" | "local"; rationale?: string; }; proposal?: { name: string; provider: string; model?: string; origin: "proposal"; rationale?: string }; usage?: UsageSummary; checkedAt: string; message: string; }; type ReferenceConfiguration = { url: string; token: string; }; type RecommendationResponse = { decision?: { proposal?: DecisionEngine; selected?: DecisionEngine; }; recommendations?: DecisionEngine[]; usage?: UsageSummary; }; type UsageMetric = { used: number; limit: number | null; percent: number | null; status: "not_configured" | "normal" | "alert" | "limit_reached" | "unpriced"; shouldBlock?: boolean }; type UsageAlert = { metric: "budget" | "requests" | "tokens"; label: string; status: "alert" | "limit_reached"; percent: number; message: string }; type UsageSummary = { canInvoke: boolean; blockReason?: string | null; budget: UsageMetric & { currency: string; estimated?: boolean }; requests: UsageMetric; tokens: UsageMetric; alerts: UsageAlert[] }; type DecisionEngine = { origin?: "proposal" | "manual" | "local"; rationale?: string; engine?: { name?: string; provider?: string; slug?: string }; connection?: { model?: string; accessMode?: "gateway" | "recommendation_only"; configured?: boolean }; }; const now = () => new Date().toISOString(); export async function getAiEngineStatus( configuration: ReferenceConfiguration = { url: ENV.aiReferentielUrl, token: ENV.aiReferentielToken, }, fetcher: typeof fetch = fetch, ): Promise { if (!configuration.url || !configuration.token) { return { state: "unconfigured", checkedAt: now(), message: "Le Référentiel IA n’est pas encore configuré pour cette application.", }; } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 6_000); try { const response = await fetcher(`${configuration.url.replace(/\/$/, "")}/api/integration/v1/recommendations`, { method: "POST", headers: { Authorization: `Bearer ${configuration.token}`, "Content-Type": "application/json", }, body: JSON.stringify({ useCase: "texte", optimizeCost: true, needsFreeTier: true, includeConnection: true, }), signal: controller.signal, }); if (!response.ok) { return { state: "unreachable", checkedAt: now(), message: `Le Référentiel IA est indisponible (HTTP ${response.status}).`, }; } const payload = (await response.json()) as RecommendationResponse; const selected = payload.decision?.selected ?? payload.recommendations?.[0]; if (!selected?.engine?.name || !selected.engine.provider || !selected.engine.slug) { return { state: "unreachable", checkedAt: now(), message: "Le Référentiel IA n’a retourné aucun moteur autorisé.", }; } const proposal = payload.decision?.proposal; const origin = selected.origin === "manual" || selected.origin === "local" ? selected.origin : "proposal"; return { state: "available", checkedAt: now(), message: selected.connection?.configured ? origin === "local" ? "Moteur IA local sélectionné par le Référentiel IA." : origin === "manual" ? "Moteur choisi par un administrateur pour cette application." : "Proposition du Référentiel IA appliquée à cette application." : "Moteur décidé par le Référentiel IA, en attente de configuration fournisseur.", engine: { name: selected.engine.name, provider: selected.engine.provider, slug: selected.engine.slug, model: selected.connection?.model, accessMode: selected.connection?.accessMode, configured: Boolean(selected.connection?.configured), origin, rationale: selected.rationale, }, proposal: proposal?.engine?.name && proposal.engine.provider ? { name: proposal.engine.name, provider: proposal.engine.provider, model: proposal.connection?.model, origin: "proposal", rationale: proposal.rationale, } : undefined, usage: payload.usage, }; } catch { return { state: "unreachable", checkedAt: now(), message: "La vérification auprès du Référentiel IA a échoué.", }; } finally { clearTimeout(timeout); } } /** * Métadonnées strictement descriptives de la décision active. Cette fonction * ne retourne jamais le jeton applicatif ni une URL d'intégration privée. */ export async function getActiveAiConfiguration( configuration: ReferenceConfiguration = { url: ENV.aiReferentielUrl, token: ENV.aiReferentielToken, }, fetcher: typeof fetch = fetch, ) { const status = await getAiEngineStatus(configuration, fetcher); if (status.state !== "available" || !status.engine?.configured || !status.engine.model) { return { active: false, engines: [] }; } return { active: true, engines: [{ provider: status.engine.provider, model: status.engine.model, role: "Moteur sélectionné par le Référentiel IA", billing: status.engine.origin === "local" ? "free" : "paid", source: "referential", }], }; }