API
Fonctions serveur du module IA, runPrompt et sa réponse, codes d'erreur, propositions gardées, consommation en jetons, intégration ai pour les autres modules et routes HTTP d'admin.
Le module s'appelle de deux façons : le projet importe @fraza/module-ai/server, et un autre module lit l'intégration ai sans importer le paquet. Les deux donnent les mêmes fonctions. Les routes HTTP ne servent qu'aux écrans de l'admin.
Règle absolue : un appel au modèle passe toujours par runPrompt, jamais par une requête directe au fournisseur. C'est lui qui vérifie la réponse, compte les jetons et écrit l'historique, et lui seul suit le fournisseur que la configuration nomme.
Import
| Sous-chemin | Runtime | Contenu |
|---|---|---|
@fraza/module-ai | serveur | manifest, register, createTables, dropTables, migrations. |
@fraza/module-ai/server | serveur | Les fonctions de cette page. Aucun React. |
@fraza/module-ai/admin | client | Enregistrement de la page IA, de la tuile du tableau de bord, et des composants AiActions et AiResults sous l'intégration ai. Aucun export. |
import { runPrompt } from '@fraza/module-ai/server';./server ne vérifie aucune permission : son appelant est le code du serveur, qui fait ses propres vérifications.
Fonctions serveur
| Fonction | Retour | Rôle |
|---|---|---|
definePrompt(definition, { source }?) | string | Déclare un prompt et rend son nom. Voir Prompts. |
customizePrompt(name, changes) | void | Ajuste un prompt pour ce projet. Voir Prompts. |
configureAi({ instructions }) | void | Instructions partagées par tous les prompts. |
listPrompts() | object[] | Les prompts déclarés : name, label, target, mode, source, customized, enabled. |
runPrompt(name, options?) | Promise<object> | Lance un prompt. Ne lève pas pour un échec d'usage normal. |
saveResult({ target, id, prompt, data, model }) | Promise<object> | Garde une réponse à côté d'un contenu. |
getResults(target, id) | Promise<object[]> | Les réponses en attente d'un contenu, la plus récente en tête. |
dismissResult(resultId) | Promise<boolean> | Marque une réponse utilisée ou ignorée. |
deleteResults(target, id) | Promise<number> | Retire les réponses d'un contenu supprimé. |
getUsage({ from, to }) | Promise<object> | Les jetons consommés sur une période. |
isAiConfigured() | boolean | Un appel peut partir. Ne lève jamais. |
describeAiConfig() | object | Ce que la page IA montre de la configuration, sans la clé. Ne lève jamais. |
runPrompt(name, options?)
const result = await runPrompt('site.summary', { context: { message } });
if (result.success) {
console.log(result.data.summary, result.usage.outputTokens);
} else {
console.warn(result.error.code, result.error.message);
}| Option | Défaut | Rôle |
|---|---|---|
input | Les réponses d'un formulaire, vérifiées contre le schéma input du prompt. | |
context | null | Ce que build() reçoit. |
scope | {} | Ce que output(scope) reçoit. |
userId | null | Le compte à qui attribuer l'appel dans l'historique. |
trigger | 'code' | 'button', 'code' ou 'test', l'origine notée dans l'historique. Une autre valeur compte comme 'code'. |
signal | Un AbortSignal qui arrête l'attente du fournisseur. |
La réponse a l'une de ces deux formes :
{ success: true, data, model, usage: { inputTokens, outputTokens } }
{ success: false, error: { code, message, retryable } }data est la réponse, déjà vérifiée contre le schéma du prompt. model est le modèle que le fournisseur dit avoir utilisé, sinon celui de la configuration. usage additionne les deux tentatives, et un compte vaut null quand le fournisseur ne le donne pas. message est en français, prêt pour l'admin. retryable dit qu'un essai plus tard peut réussir.
runPrompt ne lève que pour une erreur de programmation, jamais pour une panne, une configuration absente ou une réponse hors schéma. Le module n'a pas besoin d'être activé pour qu'un appel du code parte : FRAZA_MODULE_AI décide des routes, de l'admin et de l'intégration, pas de ./server.
Codes d'erreur
| Code | Quand | retryable | Statut de POST /ai/run |
|---|---|---|---|
unknown_prompt | Aucun prompt de ce nom, ou le projet l'a désactivé. | non | 404 |
not_configured | Le module est désactivé (FRAZA_MODULE_AI ne vaut pas true), ou une variable manque ou est illisible. Le message la nomme. | non | 503 |
invalid_input | La saisie ne correspond pas au schéma input. | non | 400 |
not_applicable | output(scope) a rendu null : le prompt ne s'applique pas à ce contenu. Aucun appel. | non | 400 |
prompt_error | output ou build() a levé, ou a rendu une forme invalide. Le détail est dans le journal du serveur. | non | 500 |
timeout | Pas de réponse dans FRAZA_MODULE_AI_TIMEOUT_MS. | oui | 502 |
unreachable | Le fournisseur est injoignable : adresse, réseau. | oui | 502 |
auth | La clé est refusée (401, 403). | non | 502 |
rate_limited | Le fournisseur demande de ralentir (429). | oui | 429 |
rejected | La demande est refusée (400, 404, 422) : modèle inconnu, schéma, image. | non | 502 |
provider | Le fournisseur a échoué (5xx), ou a répondu sans contenu lisible. | oui | 502 |
refused | Le modèle a refusé de répondre. L'historique ne garde pas son explication, écrite par le modèle. | non | 502 |
truncated | La réponse a atteint maxTokens. | non | 502 |
cancelled | Le signal de l'appelant a annulé l'appel. | non | 400 |
invalid_answer | La réponse était hors schéma deux fois de suite. | oui | 502 |
Dans une réponse d'erreur de la route, le code est dans le champ code.
saveResult, getResults, dismissResult, deleteResults
const saved = await saveResult({ target: 'medias.media', id: media.id, prompt: 'medias.alt', data: result.data, model: result.model });
// { id: '17', target: 'medias.media', targetId: 'a1b2…', prompt: 'medias.alt', data: { altText: '…' }, model: '…', createdAt: '2026-11-07T…' }target est un nom en mots minuscules séparés par des points, 120 caractères au plus. id est un entier ou une chaîne de 120 caractères au plus, gardé en texte. Une valeur hors de ces règles lève une TypeError. data est un objet, et createdAt la date de la dernière réponse.
saveResult remplace la réponse déjà gardée pour le même contenu et le même prompt, et la remontre si elle avait été ignorée. getResults ne rend que les réponses ni utilisées ni ignorées. dismissResult rend false pour un identifiant inconnu ou déjà marqué. deleteResults rend le nombre de réponses retirées. Voir Modèle de données.
getUsage({ from, to })
const to = new Date();
const from = new Date(to.getTime() - 30 * 24 * 60 * 60 * 1000);
const usage = await getUsage({ from, to });
// {
// from, to,
// totals: { calls, errors, inputTokens, outputTokens },
// byDay: [{ day: '2026-11-06', calls, errors, inputTokens, outputTokens }, …],
// byPrompt: [{ prompt, calls, errors, inputTokens, outputTokens }, …],
// byModel: [{ provider, model, calls, errors, inputTokens, outputTokens }, …],
// }from et to sont des Date, to exclue. Les jours sont coupés en UTC. byPrompt et byModel sont triés du plus gros consommateur au plus petit. Des dates invalides, ou from après to, lèvent une TypeError.
Des jetons seulement, jamais un prix : le module ne connaît pas le contrat passé avec le fournisseur. La fonction est publiée sous cette forme pour qu'un registre de consommation du core, à venir, la lise comme celle de tout module qui consomme un service payant.
describeAiConfig()
Rend { configured, provider, providerLabel, model, hasKey, baseUrl, problem }. hasKey dit si une clé est présente, jamais sa valeur. baseUrl n'est rendue que pour openai-compatible et ollama. problem est le message de la première variable en défaut, ou null.
L'intégration ai
Un module ne déclare pas @fraza/module-ai en dépendance : importer un paquet absent ferait échouer le build du site. Il lit l'intégration ai, publiée par le module quand il est activé.
import { getIntegration } from '@fraza/core/integrations';
const AI_CONTRACT = 1;
function aiOf() {
const ai = getIntegration('ai');
return ai && ai.contract === AI_CONTRACT ? ai : null;
}| Côté | Contenu |
|---|---|
| Serveur | contract, definePrompt, runPrompt, saveResult, getResults, dismissResult, deleteResults, isAiConfigured, getUsage. |
| Navigateur | AiActions et AiResults. Voir Boutons. |
Lire le contrat. contract vaut 1. Il augmente quand la forme d'une de ces fonctions change, et un module ignore une intégration dont il ne parle pas le contrat : il perd ses boutons, il ne casse pas.
Déclarer dans onModulesReady. Le module IA peut s'enregistrer après le module qui l'utilise. Un module déclare donc ses prompts dans un callback de onModulesReady(), où toutes les intégrations sont publiées. Voir Enregistrement inter-modules.
// register-server.js d'un module, exemple
import { onModulesReady } from '@fraza/core/modules';
onModulesReady(() => {
const ai = aiOf();
if (!ai) return;
ai.definePrompt({ name: 'acme.write', /* … */ }, { source: '@acme/module-x' });
});Un module passe toujours son source à definePrompt : sans lui, le prompt est attribué au projet, et c'est la source qui distingue un rechargement d'un conflit de nom.
Routes HTTP
Toutes sont montées sous /za/api, et toutes demandent une session d'admin.
| Méthode | Chemin | Permission | Rôle |
|---|---|---|---|
GET | /ai/actions?target=&scope= | ai.use | Les boutons d'un écran que ce compte peut lancer, avec leur schéma d'entrée et de sortie. scope en JSON. |
POST | /ai/run | ai.use | Lance le prompt d'un bouton. Corps : { prompt, input, context, scope }. Préréglage ai_run. |
GET | /ai/results?target=&id= | ai.use | Les propositions en attente d'un contenu. |
POST | /ai/results/:id/dismiss | ai.use | Marque une proposition utilisée ou ignorée. |
GET | /ai/status | ai.view | Configuration, prompts déclarés, ajustements sans prompt. |
POST | /ai/test | ai.view | Le court appel du bouton « Tester ». Préréglage ai_run. |
GET | /ai/runs?page=&limit=&status= | ai.view | L'historique, 50 lignes par page par défaut, 200 au plus. status : success ou error. |
GET | /ai/usage?days= | ai.view | getUsage() sur les days derniers jours, 30 par défaut, de 1 à 366. |
POST /ai/run refuse un prompt sans target par un 404, comme un prompt inconnu, et un compte sans la permission du prompt par un 403. Il rend { success: true, data, model }, sans usage.
Notes
- Les fonctions de
./serveret celles de l'intégration sont les mêmes objets : un appel par l'une ou par l'autre écrit le même historique. isAiConfigured()etdescribeAiConfig()tiennent aussi compte deFRAZA_MODULE_AI_RETENTION_DAYS: une rétention hors bornes les rend non configurés, alors querunPromptn'en dépend pas et appelle le fournisseur.- Les propositions d'un contenu se lisent et se marquent avec
ai.useseul : la route ne vérifie pas la permission du module qui possède le contenu.